biomes = new HashMap<>();
+ biomes.put(new Vector(3, 64, 5), Biome.DESERT);
+ // 403 >> 4 = 25 ; repeatCalc(25) with size 25 = floorMod(50,50)-25 = -25
+ when(chunkGen.getChunk(-25, 0)).thenReturn(new ChunkStore(null, null, null, biomes));
+
+ assertEquals(Biome.DESERT, gen.getBiome(worldInfo, 403, 64, 5));
+ }
+}
diff --git a/src/test/java/world/bentobox/boxed/generators/chunks/RepeatCalcTest.java b/src/test/java/world/bentobox/boxed/generators/chunks/RepeatCalcTest.java
new file mode 100644
index 0000000..a665aa7
--- /dev/null
+++ b/src/test/java/world/bentobox/boxed/generators/chunks/RepeatCalcTest.java
@@ -0,0 +1,67 @@
+package world.bentobox.boxed.generators.chunks;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import world.bentobox.boxed.WhiteBox;
+
+/**
+ * Tests {@link AbstractBoxedChunkGenerator#repeatCalc(int)} - the function that
+ * maps an arbitrary chunk coordinate back into the repeating seed region
+ * {@code [-size, size)}. This is what makes the small captured seed area tile
+ * infinitely across the game world.
+ */
+class RepeatCalcTest {
+
+ private void setSize(int size) {
+ WhiteBox.setInternalState(AbstractBoxedChunkGenerator.class, "size", size);
+ }
+
+ @Test
+ void testIdentityWithinRange() {
+ setSize(5);
+ // Coordinates already inside [-size, size) are returned unchanged
+ for (int c = -5; c < 5; c++) {
+ assertEquals(c, AbstractBoxedChunkGenerator.repeatCalc(c), "coord " + c);
+ }
+ }
+
+ @Test
+ void testWrapsAboveRange() {
+ setSize(5);
+ // size maps back to -size, and it keeps wrapping with period 2*size
+ assertEquals(-5, AbstractBoxedChunkGenerator.repeatCalc(5));
+ assertEquals(-4, AbstractBoxedChunkGenerator.repeatCalc(6));
+ assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(10));
+ assertEquals(4, AbstractBoxedChunkGenerator.repeatCalc(14));
+ assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(19));
+ }
+
+ @Test
+ void testWrapsBelowRange() {
+ setSize(5);
+ assertEquals(-5, AbstractBoxedChunkGenerator.repeatCalc(-5));
+ assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(-10));
+ assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(-11));
+ }
+
+ @Test
+ void testResultAlwaysWithinRange() {
+ setSize(8);
+ for (int c = -100; c <= 100; c++) {
+ int r = AbstractBoxedChunkGenerator.repeatCalc(c);
+ assertEquals(true, r >= -8 && r < 8, "coord " + c + " mapped out of range to " + r);
+ }
+ }
+
+ @Test
+ void testDifferentSize() {
+ setSize(1);
+ // With size 1 the region is just {-1, 0}
+ assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(0));
+ assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(-1));
+ assertEquals(-1, AbstractBoxedChunkGenerator.repeatCalc(1));
+ assertEquals(0, AbstractBoxedChunkGenerator.repeatCalc(2));
+ }
+}
diff --git a/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java b/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java
index 5918ef6..e36dc70 100644
--- a/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java
+++ b/src/test/java/world/bentobox/boxed/listeners/AdvancementListenerTest.java
@@ -4,7 +4,6 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -51,7 +50,7 @@
/**
* @author tastybento
*/
-public class AdvancementListenerTest extends CommonTestSetup {
+class AdvancementListenerTest extends CommonTestSetup {
@Mock
private Boxed addon;
@@ -145,7 +144,7 @@ public void tearDown() throws Exception {
// ---------- constructor ----------
@Test
- public void testConstructor() {
+ void testConstructor() {
assertNotNull(listener);
}
@@ -159,28 +158,28 @@ private PlayerAdvancementDoneEvent advancementDoneEvent() {
}
@Test
- public void testOnAdvancementNotSurvival() {
+ void testOnAdvancementNotSurvival() {
when(player.getGameMode()).thenReturn(GameMode.CREATIVE);
listener.onAdvancement(advancementDoneEvent());
verify(advManager, never()).addAdvancement(any(Player.class), any(Advancement.class));
}
@Test
- public void testOnAdvancementIgnoreSetting() {
+ void testOnAdvancementIgnoreSetting() {
settings.setIgnoreAdvancements(true);
listener.onAdvancement(advancementDoneEvent());
verify(advManager, never()).addAdvancement(any(Player.class), any(Advancement.class));
}
@Test
- public void testOnAdvancementNotInWorld() {
+ void testOnAdvancementNotInWorld() {
when(addon.inWorld(world)).thenReturn(false);
listener.onAdvancement(advancementDoneEvent());
verify(advManager, never()).addAdvancement(any(Player.class), any(Advancement.class));
}
@Test
- public void testOnAdvancementVisitorDenied() {
+ void testOnAdvancementVisitorDenied() {
settings.setDenyVisitorAdvancements(true);
// player is NOT in the member set
when(island.getMemberSet()).thenReturn(com.google.common.collect.ImmutableSet.of(UUID.randomUUID()));
@@ -195,15 +194,15 @@ public void testOnAdvancementVisitorDenied() {
}
@Test
- public void testOnAdvancementMemberGrantsAndSchedulesTellTeam() {
+ void testOnAdvancementMemberGrantsAndSchedulesTellTeam() {
listener.onAdvancement(advancementDoneEvent());
- verify(advManager).addAdvancement(eq(player), eq(advancement));
+ verify(advManager).addAdvancement(player, advancement);
// tellTeam is scheduled one tick later
verify(sch).runTask(eq(plugin), any(Runnable.class));
}
@Test
- public void testOnAdvancementZeroScoreDoesNotSchedule() {
+ void testOnAdvancementZeroScoreDoesNotSchedule() {
when(advManager.addAdvancement(any(Player.class), any(Advancement.class))).thenReturn(0);
listener.onAdvancement(advancementDoneEvent());
verify(sch, never()).runTask(eq(plugin), any(Runnable.class));
@@ -219,25 +218,25 @@ private PlayerPortalEvent portalEvent(TeleportCause cause) {
}
@Test
- public void testOnPortalNetherNoException() {
+ void testOnPortalNetherNoException() {
// With null netherAdvancement fields, giveAdv short-circuits — we just assert no throw.
listener.onPortal(portalEvent(TeleportCause.NETHER_PORTAL));
}
@Test
- public void testOnPortalEndNoException() {
+ void testOnPortalEndNoException() {
listener.onPortal(portalEvent(TeleportCause.END_PORTAL));
}
@Test
- public void testOnPortalNotSurvival() {
+ void testOnPortalNotSurvival() {
when(player.getGameMode()).thenReturn(GameMode.CREATIVE);
// should early return — no NPE even though we don't stub the cause
listener.onPortal(portalEvent(TeleportCause.NETHER_PORTAL));
}
@Test
- public void testOnPortalNotInWorld() {
+ void testOnPortalNotInWorld() {
when(addon.inWorld(world)).thenReturn(false);
listener.onPortal(portalEvent(TeleportCause.NETHER_PORTAL));
}
@@ -245,21 +244,21 @@ public void testOnPortalNotInWorld() {
// ---------- syncAdvancements ----------
@Test
- public void testSyncAdvancementsIgnoreSetting() {
+ void testSyncAdvancementsIgnoreSetting() {
settings.setIgnoreAdvancements(true);
listener.syncAdvancements(user);
verify(user, never()).sendMessage(anyString(), any());
}
@Test
- public void testSyncAdvancementsNoIsland() {
+ void testSyncAdvancementsNoIsland() {
when(im.getIsland(world, user)).thenReturn(null);
listener.syncAdvancements(user);
verify(user, never()).sendMessage(anyString(), any());
}
@Test
- public void testSyncAdvancementsSizeIncreased() {
+ void testSyncAdvancementsSizeIncreased() {
when(advManager.checkIslandSize(island)).thenReturn(3);
// Return a non-null IslandAdvancements stub for grantAdv's iteration
when(advManager.getIsland(island))
@@ -270,7 +269,7 @@ public void testSyncAdvancementsSizeIncreased() {
}
@Test
- public void testSyncAdvancementsSizeDecreased() {
+ void testSyncAdvancementsSizeDecreased() {
when(advManager.checkIslandSize(island)).thenReturn(-2);
when(advManager.getIsland(island))
.thenReturn(mock(world.bentobox.boxed.objects.IslandAdvancements.class));
@@ -281,7 +280,7 @@ public void testSyncAdvancementsSizeDecreased() {
// ---------- onPlayerJoin / onPlayerEnterWorld ----------
@Test
- public void testOnPlayerJoinNotInWorld() {
+ void testOnPlayerJoinNotInWorld() {
when(addon.inWorld(world)).thenReturn(false);
PlayerJoinEvent e = mock(PlayerJoinEvent.class);
when(e.getPlayer()).thenReturn(player);
@@ -291,7 +290,7 @@ public void testOnPlayerJoinNotInWorld() {
}
@Test
- public void testOnPlayerEnterWorldDifferentWorld() {
+ void testOnPlayerEnterWorldDifferentWorld() {
World otherWorld = mock(World.class);
when(otherWorld.getName()).thenReturn("other_world");
when(world.getName()).thenReturn("boxed_world");
@@ -307,7 +306,7 @@ public void testOnPlayerEnterWorldDifferentWorld() {
// ---------- onTeamJoinTime / onTeamLeaveTime / onFirstTime ----------
@Test
- public void testOnTeamJoinTimeSettingDisabled() {
+ void testOnTeamJoinTimeSettingDisabled() {
// isOnJoinResetAdvancements defaults to false
TeamJoinedEvent e = mock(TeamJoinedEvent.class);
when(e.getPlayerUUID()).thenReturn(playerUuid);
@@ -316,7 +315,7 @@ public void testOnTeamJoinTimeSettingDisabled() {
}
@Test
- public void testOnTeamLeaveTimeIgnoreAdvancements() {
+ void testOnTeamLeaveTimeIgnoreAdvancements() {
settings.setIgnoreAdvancements(true);
TeamLeaveEvent e = mock(TeamLeaveEvent.class);
listener.onTeamLeaveTime(e);
@@ -325,7 +324,7 @@ public void testOnTeamLeaveTimeIgnoreAdvancements() {
}
@Test
- public void testOnFirstTimeIgnoreAdvancements() {
+ void testOnFirstTimeIgnoreAdvancements() {
settings.setIgnoreAdvancements(true);
IslandNewIslandEvent e = mock(IslandNewIslandEvent.class);
listener.onFirstTime(e);
@@ -333,7 +332,7 @@ public void testOnFirstTimeIgnoreAdvancements() {
}
@Test
- public void testOnFirstTimeNotInBoxedWorld() {
+ void testOnFirstTimeNotInBoxedWorld() {
IslandNewIslandEvent e = mock(IslandNewIslandEvent.class);
when(e.getIsland()).thenReturn(island);
when(island.getWorld()).thenReturn(world);
@@ -347,12 +346,12 @@ public void testOnFirstTimeNotInBoxedWorld() {
// ---------- static helpers ----------
@Test
- public void testGetAdvancementNotFound() {
+ void testGetAdvancementNotFound() {
assertNull(AdvancementListener.getAdvancement("minecraft:story/nonexistent"));
}
@Test
- public void testGetAdvancementFound() {
+ void testGetAdvancementFound() {
Advancement a = mock(Advancement.class);
when(a.getKey()).thenReturn(NamespacedKey.fromString("minecraft:story/root"));
mockedBukkit.when(Bukkit::advancementIterator).thenAnswer(inv -> List.of(a).iterator());
@@ -360,21 +359,21 @@ public void testGetAdvancementFound() {
}
@Test
- public void testGiveAdvNullAdvancement() {
+ void testGiveAdvNullAdvancement() {
// No throw, no interaction
AdvancementListener.giveAdv(player, null);
verify(player, never()).getAdvancementProgress(any(Advancement.class));
}
@Test
- public void testGiveAdvNotDoneAwardsCriteria() {
+ void testGiveAdvNotDoneAwardsCriteria() {
AdvancementListener.giveAdv(player, advancement);
verify(progress).awardCriteria("crit1");
verify(progress).awardCriteria("crit2");
}
@Test
- public void testGiveAdvAlreadyDoneNoOp() {
+ void testGiveAdvAlreadyDoneNoOp() {
when(progress.isDone()).thenReturn(true);
AdvancementListener.giveAdv(player, advancement);
verify(progress, never()).awardCriteria(anyString());
diff --git a/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java b/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java
index 17555ae..90ce454 100644
--- a/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java
+++ b/src/test/java/world/bentobox/boxed/listeners/EnderPearlListenerTest.java
@@ -53,7 +53,7 @@
* @author tastybento
*
*/
-public class EnderPearlListenerTest extends CommonTestSetup {
+class EnderPearlListenerTest extends CommonTestSetup {
@Mock
private Boxed addon;
@@ -158,7 +158,7 @@ public void tearDown() throws Exception {
* Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#EnderPearlListener(world.bentobox.boxed.Boxed)}.
*/
@Test
- public void testEnderPearlListener() {
+ void testEnderPearlListener() {
assertNotNull(epl);
}
@@ -166,7 +166,7 @@ public void testEnderPearlListener() {
* Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}.
*/
@Test
- public void testOnPlayerTeleportNotAllowed() {
+ void testOnPlayerTeleportNotAllowed() {
PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT);
epl.onPlayerTeleport(e);
assertTrue(e.isCancelled());
@@ -177,7 +177,7 @@ public void testOnPlayerTeleportNotAllowed() {
* Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}.
*/
@Test
- public void testOnPlayerTeleportNotSurvival() {
+ void testOnPlayerTeleportNotSurvival() {
when(player.getGameMode()).thenReturn(GameMode.CREATIVE);
PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT);
epl.onPlayerTeleport(e);
@@ -189,7 +189,7 @@ public void testOnPlayerTeleportNotSurvival() {
* Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}.
*/
@Test
- public void testOnPlayerTeleportNullTo() {
+ void testOnPlayerTeleportNullTo() {
when(player.getGameMode()).thenReturn(GameMode.CREATIVE);
PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, null, TeleportCause.CHORUS_FRUIT);
epl.onPlayerTeleport(e);
@@ -201,7 +201,7 @@ public void testOnPlayerTeleportNullTo() {
* Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}.
*/
@Test
- public void testOnPlayerTeleportToSpawn() {
+ void testOnPlayerTeleportToSpawn() {
when(spawn.onIsland(any())).thenReturn(true);
PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT);
epl.onPlayerTeleport(e);
@@ -213,7 +213,7 @@ public void testOnPlayerTeleportToSpawn() {
* Test method for {@link world.bentobox.boxed.listeners.EnderPearlListener#onPlayerTeleport(org.bukkit.event.player.PlayerTeleportEvent)}.
*/
@Test
- public void testOnPlayerTeleportNotInWorldAllowed() {
+ void testOnPlayerTeleportNotInWorldAllowed() {
when(addon.inWorld(any(World.class))).thenReturn(false);
when(addon.inWorld(any(Location.class))).thenReturn(false);
PlayerTeleportEvent e = new PlayerTeleportEvent(player, from, to, TeleportCause.CHORUS_FRUIT);
@@ -227,7 +227,7 @@ public void testOnPlayerTeleportNotInWorldAllowed() {
* @throws IOException
*/
@Test
- public void testOnEnderPearlLandNotEnderPearl() throws IOException {
+ void testOnEnderPearlLandNotEnderPearl() throws IOException {
when(projectile.getType()).thenReturn(EntityType.ARROW);
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
@@ -241,7 +241,7 @@ public void testOnEnderPearlLandNotEnderPearl() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlLandNullHitBlock() throws IOException {
+ void testOnEnderPearlLandNullHitBlock() throws IOException {
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, null, BlockFace.UP);
epl.onEnderPearlLand(e);
assertFalse(e.isCancelled());
@@ -254,7 +254,7 @@ public void testOnEnderPearlLandNullHitBlock() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlLandNotInWorld() throws IOException {
+ void testOnEnderPearlLandNotInWorld() throws IOException {
when(addon.inWorld(to)).thenReturn(false);
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
@@ -268,7 +268,7 @@ public void testOnEnderPearlLandNotInWorld() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlLandNotMovingBox() throws IOException {
+ void testOnEnderPearlLandNotMovingBox() throws IOException {
Boxed.ALLOW_MOVE_BOX.setSetting(world, false);
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
@@ -282,7 +282,7 @@ public void testOnEnderPearlLandNotMovingBox() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlLandNonHuman() throws IOException {
+ void testOnEnderPearlLandNonHuman() throws IOException {
Creeper creeper = mock(Creeper.class);
when(projectile.getShooter()).thenReturn(creeper);
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
@@ -297,7 +297,7 @@ public void testOnEnderPearlLandNonHuman() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlLandUserHasNoIsland() throws IOException {
+ void testOnEnderPearlLandUserHasNoIsland() throws IOException {
when(im.getIsland(world, user)).thenReturn(null);
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
@@ -311,7 +311,7 @@ public void testOnEnderPearlLandUserHasNoIsland() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlNotOnIslandWhenThrowing() throws IOException {
+ void testOnEnderPearlNotOnIslandWhenThrowing() throws IOException {
when(im.getIslandAt(any())).thenReturn(Optional.empty());
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
@@ -332,7 +332,7 @@ private void verifyFailure() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlLandHuman() throws IOException {
+ void testOnEnderPearlLandHuman() throws IOException {
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
assertFalse(e.isCancelled());
@@ -348,8 +348,8 @@ public void testOnEnderPearlLandHuman() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlThrewToDifferentIsland() throws IOException {
- when(im.getIslandAt(eq(to))).thenReturn(Optional.of(anotherIsland));
+ void testOnEnderPearlThrewToDifferentIsland() {
+ when(im.getIslandAt(to)).thenReturn(Optional.of(anotherIsland));
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
assertTrue(e.isCancelled());
@@ -361,8 +361,8 @@ public void testOnEnderPearlThrewToDifferentIsland() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlThrewToNonIsland() throws IOException {
- when(im.getIslandAt(eq(to))).thenReturn(Optional.empty());
+ void testOnEnderPearlThrewToNonIsland() {
+ when(im.getIslandAt(to)).thenReturn(Optional.empty());
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
assertTrue(e.isCancelled());
@@ -374,7 +374,7 @@ public void testOnEnderPearlThrewToNonIsland() throws IOException {
* @throws IOException
*/
@Test
- public void testOnEnderPearlCannotSetProtectionCenter() throws IOException {
+ void testOnEnderPearlCannotSetProtectionCenter() throws IOException {
doThrow(IOException.class).when(island).setProtectionCenter(to);
ProjectileHitEvent e = new ProjectileHitEvent(projectile, null, hitBlock, BlockFace.UP);
epl.onEnderPearlLand(e);
diff --git a/src/test/java/world/bentobox/boxed/objects/BoxedJigsawBlockTest.java b/src/test/java/world/bentobox/boxed/objects/BoxedJigsawBlockTest.java
new file mode 100644
index 0000000..cae90f4
--- /dev/null
+++ b/src/test/java/world/bentobox/boxed/objects/BoxedJigsawBlockTest.java
@@ -0,0 +1,58 @@
+package world.bentobox.boxed.objects;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import com.google.gson.Gson;
+
+/**
+ * Tests {@link BoxedJigsawBlock} GSON (de)serialization.
+ *
+ * The {@code finalState} field is mapped to the Minecraft jigsaw JSON key
+ * {@code final_state} via {@link com.google.gson.annotations.SerializedName}.
+ * These tests guard that mapping so the field can keep a Java-friendly name
+ * without breaking the structure data that {@code NewAreaListener} reads with a
+ * plain {@code new Gson()}.
+ */
+class BoxedJigsawBlockTest {
+
+ // Production (NewAreaListener) deserialises with a plain Gson instance.
+ private final Gson gson = new Gson();
+
+ private static final String JSON = "{\"final_state\":\"minecraft:polished_blackstone_bricks\","
+ + "\"joint\":\"aligned\",\"name\":\"minecraft:empty\","
+ + "\"pool\":\"minecraft:bastion/bridge/legs\",\"target\":\"minecraft:leg_connector\"}";
+
+ /**
+ * The key in the source data is {@code final_state}; it must still populate the
+ * renamed {@code finalState} field.
+ */
+ @Test
+ void testDeserialiseFinalStateKey() {
+ BoxedJigsawBlock bjb = gson.fromJson(JSON, BoxedJigsawBlock.class);
+ assertEquals("minecraft:polished_blackstone_bricks", bjb.getFinalState());
+ assertEquals("aligned", bjb.getJoint());
+ assertEquals("minecraft:empty", bjb.getName());
+ assertEquals("minecraft:bastion/bridge/legs", bjb.getPool());
+ assertEquals("minecraft:leg_connector", bjb.getTarget());
+ }
+
+ /**
+ * Serialisation must emit the {@code final_state} key, not {@code finalState}.
+ */
+ @Test
+ void testSerialiseUsesFinalStateKey() {
+ BoxedJigsawBlock bjb = gson.fromJson(JSON, BoxedJigsawBlock.class);
+ String json = gson.toJson(bjb);
+ assertTrue(json.contains("\"final_state\""), "Expected final_state key in: " + json);
+ assertTrue(json.contains("minecraft:polished_blackstone_bricks"));
+ }
+
+ @Test
+ void testToStringUsesFinalState() {
+ BoxedJigsawBlock bjb = gson.fromJson(JSON, BoxedJigsawBlock.class);
+ assertTrue(bjb.toString().contains("finalState=minecraft:polished_blackstone_bricks"));
+ }
+}
diff --git a/src/test/java/world/bentobox/boxed/objects/BoxedStructureBlockTest.java b/src/test/java/world/bentobox/boxed/objects/BoxedStructureBlockTest.java
new file mode 100644
index 0000000..f9250c9
--- /dev/null
+++ b/src/test/java/world/bentobox/boxed/objects/BoxedStructureBlockTest.java
@@ -0,0 +1,79 @@
+package world.bentobox.boxed.objects;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.bukkit.block.data.type.StructureBlock.Mode;
+import org.bukkit.block.structure.Mirror;
+import org.bukkit.block.structure.StructureRotation;
+import org.junit.jupiter.api.Test;
+
+import com.google.gson.Gson;
+
+/**
+ * Tests {@link BoxedStructureBlock} GSON deserialization (this is how the class
+ * is populated from captured structure-block data) and its {@code toString()}.
+ */
+class BoxedStructureBlockTest {
+
+ // NewAreaListener reads structure data with a plain Gson instance.
+ private final Gson gson = new Gson();
+
+ private static final String JSON = "{\"author\":\"LadyAgnes\",\"ignoreEntities\":true,\"integrity\":1.0,"
+ + "\"metadata\":\"drowned\",\"mirror\":\"NONE\",\"mode\":\"DATA\",\"name\":\"house\","
+ + "\"posX\":3,\"posY\":1,\"posZ\":-7,\"powered\":false,\"rotation\":\"CLOCKWISE_90\","
+ + "\"seed\":\"0\",\"showair\":false,\"showboundingbox\":true,"
+ + "\"sizeX\":5,\"sizeY\":6,\"sizeZ\":7}";
+
+ private BoxedStructureBlock deserialise() {
+ return gson.fromJson(JSON, BoxedStructureBlock.class);
+ }
+
+ @Test
+ void testStringFields() {
+ BoxedStructureBlock b = deserialise();
+ assertEquals("LadyAgnes", b.getAuthor());
+ assertEquals("drowned", b.getMetadata());
+ assertEquals("house", b.getName());
+ assertEquals("0", b.getSeed());
+ }
+
+ @Test
+ void testEnumFields() {
+ BoxedStructureBlock b = deserialise();
+ assertEquals(Mirror.NONE, b.getMirror());
+ assertEquals(Mode.DATA, b.getMode());
+ assertEquals(StructureRotation.CLOCKWISE_90, b.getRotation());
+ }
+
+ @Test
+ void testNumericFields() {
+ BoxedStructureBlock b = deserialise();
+ assertEquals(1.0f, b.getIntegrity());
+ assertEquals(3, b.getPosX());
+ assertEquals(1, b.getPosY());
+ assertEquals(-7, b.getPosZ());
+ assertEquals(5, b.getSizeX());
+ assertEquals(6, b.getSizeY());
+ assertEquals(7, b.getSizeZ());
+ }
+
+ @Test
+ void testBooleanFields() {
+ BoxedStructureBlock b = deserialise();
+ assertTrue(b.isIgnoreEntities());
+ assertFalse(b.isPowered());
+ assertFalse(b.isShowair());
+ assertTrue(b.isShowboundingbox());
+ }
+
+ @Test
+ void testToString() {
+ String s = deserialise().toString();
+ assertTrue(s.startsWith("BoxedStructureBlock ["));
+ assertTrue(s.contains("author=LadyAgnes"));
+ assertTrue(s.contains("mode=DATA"));
+ assertTrue(s.contains("sizeZ=7"));
+ }
+}
diff --git a/src/test/java/world/bentobox/boxed/objects/IslandAdvancementsTest.java b/src/test/java/world/bentobox/boxed/objects/IslandAdvancementsTest.java
new file mode 100644
index 0000000..32edd27
--- /dev/null
+++ b/src/test/java/world/bentobox/boxed/objects/IslandAdvancementsTest.java
@@ -0,0 +1,46 @@
+package world.bentobox.boxed.objects;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IslandAdvancements} - the per-island list of earned advancements.
+ */
+class IslandAdvancementsTest {
+
+ private IslandAdvancements ia;
+
+ @BeforeEach
+ void setUp() {
+ ia = new IslandAdvancements("island-1");
+ }
+
+ @Test
+ void testConstructorSetsUniqueId() {
+ assertEquals("island-1", ia.getUniqueId());
+ }
+
+ @Test
+ void testSetUniqueId() {
+ ia.setUniqueId("island-2");
+ assertEquals("island-2", ia.getUniqueId());
+ }
+
+ @Test
+ void testAdvancementsStartEmpty() {
+ assertTrue(ia.getAdvancements().isEmpty());
+ }
+
+ @Test
+ void testSetAndGetAdvancements() {
+ List advs = List.of("minecraft:story/mine_stone", "minecraft:story/upgrade_tools");
+ ia.setAdvancements(advs);
+ assertEquals(2, ia.getAdvancements().size());
+ assertTrue(ia.getAdvancements().contains("minecraft:story/mine_stone"));
+ }
+}
diff --git a/src/test/java/world/bentobox/boxed/objects/IslandStructuresTest.java b/src/test/java/world/bentobox/boxed/objects/IslandStructuresTest.java
new file mode 100644
index 0000000..fad5d06
--- /dev/null
+++ b/src/test/java/world/bentobox/boxed/objects/IslandStructuresTest.java
@@ -0,0 +1,69 @@
+package world.bentobox.boxed.objects;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.bukkit.util.BoundingBox;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IslandStructures} - the per-island record of placed structures.
+ */
+class IslandStructuresTest {
+
+ private IslandStructures is;
+
+ @BeforeEach
+ void setUp() {
+ is = new IslandStructures("island-123");
+ }
+
+ @Test
+ void testConstructorSetsUniqueId() {
+ assertEquals("island-123", is.getUniqueId());
+ }
+
+ @Test
+ void testSetUniqueId() {
+ is.setUniqueId("other");
+ assertEquals("other", is.getUniqueId());
+ }
+
+ @Test
+ void testMapsStartEmpty() {
+ assertTrue(is.getStructureBoundingBoxMap().isEmpty());
+ assertTrue(is.getNetherStructureBoundingBoxMap().isEmpty());
+ }
+
+ @Test
+ void testAddStructure() {
+ BoundingBox bb = new BoundingBox(0, 0, 0, 16, 16, 16);
+ is.addStructure(bb, "minecraft:village");
+ assertEquals(1, is.getStructureBoundingBoxMap().size());
+ assertEquals("minecraft:village", is.getStructureBoundingBoxMap().get(bb));
+ // The nether map is untouched
+ assertTrue(is.getNetherStructureBoundingBoxMap().isEmpty());
+ }
+
+ @Test
+ void testAddNetherStructure() {
+ BoundingBox bb = new BoundingBox(0, 0, 0, 8, 8, 8);
+ is.addNetherStructure(bb, "minecraft:fortress");
+ assertEquals(1, is.getNetherStructureBoundingBoxMap().size());
+ assertEquals("minecraft:fortress", is.getNetherStructureBoundingBoxMap().get(bb));
+ assertTrue(is.getStructureBoundingBoxMap().isEmpty());
+ }
+
+ @Test
+ void testGettersLazilyRecreateNullMaps() {
+ is.setStructureBoundingBoxMap(null);
+ is.setNetherStructureBoundingBoxMap(null);
+ assertTrue(is.getStructureBoundingBoxMap().isEmpty());
+ assertTrue(is.getNetherStructureBoundingBoxMap().isEmpty());
+ // And adding after a null reset still works
+ BoundingBox bb = new BoundingBox(0, 0, 0, 1, 1, 1);
+ is.addStructure(bb, "minecraft:shipwreck");
+ assertEquals("minecraft:shipwreck", is.getStructureBoundingBoxMap().get(bb));
+ }
+}
diff --git a/src/test/java/world/bentobox/boxed/objects/ToBePlacedStructuresTest.java b/src/test/java/world/bentobox/boxed/objects/ToBePlacedStructuresTest.java
new file mode 100644
index 0000000..b768110
--- /dev/null
+++ b/src/test/java/world/bentobox/boxed/objects/ToBePlacedStructuresTest.java
@@ -0,0 +1,96 @@
+package world.bentobox.boxed.objects;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.bukkit.Location;
+import org.bukkit.block.structure.Mirror;
+import org.bukkit.block.structure.StructureRotation;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import world.bentobox.bentobox.util.Pair;
+import world.bentobox.boxed.objects.ToBePlacedStructures.StructureRecord;
+
+/**
+ * Tests {@link ToBePlacedStructures} and its {@link StructureRecord} component.
+ */
+class ToBePlacedStructuresTest {
+
+ private ToBePlacedStructures tbps;
+
+ @BeforeEach
+ void setUp() {
+ tbps = new ToBePlacedStructures();
+ }
+
+ @Test
+ void testDefaultUniqueId() {
+ assertEquals("ToDo", tbps.getUniqueId());
+ }
+
+ @Test
+ void testSetUniqueId() {
+ tbps.setUniqueId("changed");
+ assertEquals("changed", tbps.getUniqueId());
+ }
+
+ @Test
+ void testReadyToBuildStartsEmpty() {
+ assertTrue(tbps.getReadyToBuild().isEmpty());
+ }
+
+ @Test
+ void testGetterLazilyRecreatesNullMap() {
+ tbps.setReadyToBuild(null);
+ assertTrue(tbps.getReadyToBuild().isEmpty());
+ }
+
+ @Test
+ void testSetAndGetReadyToBuild() {
+ Map, List> map = new HashMap<>();
+ StructureRecord rec = new StructureRecord("village", "minecraft:village",
+ new Location(null, 1, 2, 3), StructureRotation.NONE, Mirror.NONE, false, new HashMap<>());
+ map.put(new Pair<>(0, 0), new ArrayList<>(List.of(rec)));
+ tbps.setReadyToBuild(map);
+ assertSame(map, tbps.getReadyToBuild());
+ assertEquals(1, tbps.getReadyToBuild().get(new Pair<>(0, 0)).size());
+ }
+
+ @Test
+ void testStructureRecordAccessors() {
+ Location loc = new Location(null, 10, 64, -20);
+ Map removed = new HashMap<>();
+ StructureRecord rec = new StructureRecord("name", "minecraft:igloo", loc,
+ StructureRotation.CLOCKWISE_90, Mirror.LEFT_RIGHT, true, removed);
+ assertEquals("name", rec.name());
+ assertEquals("minecraft:igloo", rec.structure());
+ assertSame(loc, rec.location());
+ assertEquals(StructureRotation.CLOCKWISE_90, rec.rot());
+ assertEquals(Mirror.LEFT_RIGHT, rec.mirror());
+ assertTrue(rec.noMobs());
+ assertSame(removed, rec.removedBlocks());
+ }
+
+ @Test
+ void testStructureRecordEqualityAndToString() {
+ Location loc = new Location(null, 0, 0, 0);
+ Map removed = new HashMap<>();
+ StructureRecord a = new StructureRecord("n", "s", loc, StructureRotation.NONE, Mirror.NONE, false, removed);
+ StructureRecord b = new StructureRecord("n", "s", loc, StructureRotation.NONE, Mirror.NONE, false, removed);
+ StructureRecord different = new StructureRecord("other", "s", loc, StructureRotation.NONE, Mirror.NONE, false,
+ removed);
+ // Records get value-based equals/hashCode for free
+ assertEquals(a, b);
+ assertEquals(a.hashCode(), b.hashCode());
+ assertNotEquals(a, different);
+ assertTrue(a.toString().contains("name=n"));
+ }
+}