diff --git a/pom.xml b/pom.xml index 7ed1ba8..89e3181 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ -LOCAL - 3.4.0 + 3.4.1 BentoBoxWorld_Boxed bentobox-world diff --git a/release-notes-3.4.0.md b/release-notes-3.4.0.md new file mode 100644 index 0000000..3a8bfff --- /dev/null +++ b/release-notes-3.4.0.md @@ -0,0 +1,49 @@ +Boxed 3.4.0 brings support for **Trial Chambers** and their trial spawners, fixes cross-game-mode advancement resets, and modernises the entire build and test stack for Paper 1.21.11 / BentoBox 3.13.0. + +## Highlights + +* **Trial Chambers support** — Boxed now captures and restores Trial Spawner state (including the normal *and* ominous configurations) when structures are pulled from the seed world into a player's box, and recognises `trial_chambers` as a tracked structure. +* **No more cross-game-mode progress loss** — Boxed no longer clears a player's advancements and statistics when an island is reset in a *different*, non-Boxed game mode. +* **Modernised build & test stack** — upgraded to Paper 1.21.11 and BentoBox 3.13.0, with the test suite migrated to JUnit 5 + Mockito + MockBukkit. + +## New Features + +### Trial Chambers & Trial Spawners + +Trial Chambers can now appear inside player boxes. When a box is generated from the seed world, Boxed copies the Trial Spawner tile-entity data so the spawners function correctly, preserving whether each spawner is in its **normal** or **ominous** configuration. Trial Chambers are also tracked for advancement-driven box growth. [[PR #123](https://github.com/BentoBoxWorld/Boxed/pull/123)] + +## Bug Fixes + +* **Advancement/statistic reset leaked across game modes** — `IslandNewIslandEvent` handling now no-ops unless the reset island belongs to a Boxed world, so resetting an island in another game mode no longer wipes Boxed progress. [[PR #125](https://github.com/BentoBoxWorld/Boxed/pull/125)] +* **Structure pasting into deleted islands** — pending structure pastes are now cancelled when an island is deleted, preventing structures being placed into a box that no longer exists. +* **Ominous trial spawners restored incorrectly** — trial spawners now restore the correct configuration rather than always applying the normal one. + +## Other Improvements + +* **Test suite migration** — replaced the PowerMock-based setup with JUnit 5 (Jupiter), Mockito `mockStatic`, and MockBukkit, and expanded coverage with new listener and placeholder tests (113 tests). +* **Reliable test dependency** — MockBukkit is now pinned to the stable Maven Central artifact `org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.110.0` instead of a floating jitpack snapshot that could break the build without any code change. +* **Documentation** — the README now documents Flags, Placeholders, `structures.yml`, config options, and Regionerator usage. [[PR #124](https://github.com/BentoBoxWorld/Boxed/pull/124)] + +## Compatibility + +✔️ BentoBox API 3.13.0 +✔️ Minecraft 1.21.x – 26.1.x (Paper 1.21.11) +✔️ Java 21 + +## Upgrading + +1. Stop your server. +2. Replace the old `Boxed` jar in `plugins/BentoBox/addons` with this release. +3. Ensure BentoBox is **3.13.0 or newer**. +4. Start the server. + +> **Note:** Trial Chambers are captured from the seed world when a box is generated, so boxes created *before* 3.4.0 will not retroactively gain Trial Chambers. New boxes (and newly expanded regions) will include them. + +## What's Changed + +* Add trial chambers support: fix trial spawner tile entity copying and advancement tracking by @Copilot in https://github.com/BentoBoxWorld/Boxed/pull/123 +* Update Boxed README: add Flags, Placeholders, structures.yml, config options, and Regionerator docs by @Copilot in https://github.com/BentoBoxWorld/Boxed/pull/124 +* Prevent Boxed from resetting player progress on non-Boxed island resets by @Copilot in https://github.com/BentoBoxWorld/Boxed/pull/125 +* Release 3.4.0 (Paper 1.21.11 / BentoBox 3.13.0, JUnit 5 + MockBukkit test migration, build fixes) by @tastybento in https://github.com/BentoBoxWorld/Boxed/pull/126 + +**Full Changelog**: https://github.com/BentoBoxWorld/Boxed/compare/3.3.0...3.4.0 diff --git a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java index 4e4d7b2..a6a492e 100644 --- a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java +++ b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java @@ -85,6 +85,7 @@ public boolean canExecute(User user, String label, List args) { // Initialize sr = StructureRotation.NONE; mirror = Mirror.NONE; + noMobs = false; // Check world if (!((Boxed) getAddon()).inWorld(getWorld())) { @@ -100,7 +101,7 @@ public boolean canExecute(User user, String label, List args) { * 6. place ~ ~ ~ ROTATION MIRROR * 7. place ~ ~ ~ ROTATION MIRROR NO_MOBS */ - if (args.isEmpty() || args.size() == 2 || args.size() == 3 || args.size() > 6) { + if (args.isEmpty() || args.size() == 2 || args.size() == 3 || args.size() > 7) { this.showHelp(this, user); return false; } @@ -246,8 +247,7 @@ private boolean undoLastPlacement(User user) { if (lastRecord.removedBlocks().containsKey(v)) { return lastRecord.removedBlocks().get(v).createBlockState(); } - BlockState airState = Material.AIR.createBlockData().createBlockState(); - return airState; + return Material.AIR.createBlockData().createBlockState(); }; s.place( diff --git a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java index c94e27d..3344f84 100644 --- a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java +++ b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedBlockPopulator.java @@ -11,7 +11,6 @@ import org.bukkit.block.BlockState; import org.bukkit.block.CreatureSpawner; import org.bukkit.block.TrialSpawner; -import org.bukkit.spawner.TrialSpawnerConfiguration; import org.bukkit.entity.Entity; import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.LimitedRegion; diff --git a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java index fd6dd38..48547ed 100644 --- a/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java +++ b/src/main/java/world/bentobox/boxed/generators/chunks/BoxedChunkGenerator.java @@ -73,7 +73,7 @@ protected List getEnts(Chunk chunk) { return this.setEntities(Arrays.stream(chunk.getEntities()) .filter(Objects::nonNull) .filter(e -> !(e instanceof Player)) - .filter(e -> e instanceof LivingEntity) + .filter(LivingEntity.class::isInstance) .map(LivingEntity.class::cast) .toList()); } @@ -98,14 +98,14 @@ private List setEntities(Collection entities) { bpe.setColor(c.getColor()); } } - if (entity instanceof Tameable) { - bpe.setTamed(((Tameable)entity).isTamed()); + if (entity instanceof Tameable tameable) { + bpe.setTamed(tameable.isTamed()); } - if (entity instanceof ChestedHorse) { - bpe.setChest(((ChestedHorse)entity).isCarryingChest()); + if (entity instanceof ChestedHorse chestedHorse) { + bpe.setChest(chestedHorse.isCarryingChest()); } // Only set if child. Most animals are adults - if (entity instanceof Ageable && !((Ageable)entity).isAdult()) { + if (entity instanceof Ageable ageable && !ageable.isAdult()) { bpe.setAdult(false); } if (entity instanceof AbstractHorse horse) { diff --git a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java index a5d306b..18577da 100644 --- a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java @@ -53,7 +53,6 @@ import world.bentobox.bentobox.api.events.island.IslandResettedEvent; import world.bentobox.bentobox.database.Database; import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.nms.AbstractMetaData; import world.bentobox.bentobox.util.Pair; import world.bentobox.bentobox.util.Util; import world.bentobox.boxed.Boxed; @@ -236,7 +235,7 @@ public void onChunkLoad(ChunkLoadEvent e) { if (!(addon.inWorld(chunk.getWorld()))) { return; } - Pair chunkCoords = new Pair(chunk.getX(), chunk.getZ()); + Pair chunkCoords = new Pair<>(chunk.getX(), chunk.getZ()); if (pending.containsKey(chunkCoords)) { Iterator it = pending.get(chunkCoords).iterator(); while (it.hasNext()) { @@ -368,7 +367,7 @@ public void onIslandDeleted(IslandDeleteEvent event) { for (List records : pending.values()) { records.removeIf(record -> event.getIsland().inIslandSpace(record.location())); } - pending.values().removeIf(list -> list.isEmpty()); + pending.values().removeIf(List::isEmpty); // Remove from pending structures in database Map, List> readyToBuild = loadToDos().getReadyToBuild(); @@ -578,7 +577,7 @@ private static void processJigsaw(Block b, StructureRotation structureRotation, return; } BoxedJigsawBlock bjb = gson.fromJson(data, BoxedJigsawBlock.class); - String finalState = correctDirection(bjb.getFinal_state(), structureRotation); + String finalState = correctDirection(bjb.getFinalState(), structureRotation); BlockData bd = Bukkit.createBlockData(finalState); b.setBlockData(bd); if (!bjb.getPool().equalsIgnoreCase("minecraft:empty") && pasteMobs) { diff --git a/src/main/java/world/bentobox/boxed/objects/BoxedJigsawBlock.java b/src/main/java/world/bentobox/boxed/objects/BoxedJigsawBlock.java index b06e892..48240bc 100644 --- a/src/main/java/world/bentobox/boxed/objects/BoxedJigsawBlock.java +++ b/src/main/java/world/bentobox/boxed/objects/BoxedJigsawBlock.java @@ -1,6 +1,7 @@ package world.bentobox.boxed.objects; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; /** * This replicates a jigsaw block. @@ -10,7 +11,8 @@ public class BoxedJigsawBlock { // final_state:"minecraft:polished_blackstone_bricks",joint:"aligned",name:"minecraft:empty",pool:"minecraft:bastion/bridge/legs",target:"minecraft:leg_connector" @Expose - private String final_state; + @SerializedName("final_state") + private String finalState; @Expose private String joint; @Expose @@ -20,10 +22,10 @@ public class BoxedJigsawBlock { @Expose private String target; /** - * @return the final_state + * @return the finalState */ - public String getFinal_state() { - return final_state; + public String getFinalState() { + return finalState; } /** * @return the joint @@ -51,7 +53,7 @@ public String getTarget() { } @Override public String toString() { - return "BoxedJigsawBlock [" + (final_state != null ? "final_state=" + final_state + ", " : "") + return "BoxedJigsawBlock [" + (finalState != null ? "finalState=" + finalState + ", " : "") + (joint != null ? "joint=" + joint + ", " : "") + (name != null ? "name=" + name + ", " : "") + (pool != null ? "pool=" + pool + ", " : "") + (target != null ? "target=" + target : "") + "]"; } diff --git a/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java b/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java index 6bc561a..a36eb7a 100644 --- a/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java +++ b/src/test/java/world/bentobox/boxed/AdvancementsManagerTest.java @@ -42,7 +42,7 @@ * @author tastybento * */ -public class AdvancementsManagerTest extends CommonTestSetup { +class AdvancementsManagerTest extends CommonTestSetup { @Mock private world.bentobox.bentobox.Settings pluginSettings; @@ -129,7 +129,7 @@ public void tearDown() throws Exception { * @throws Exception */ @Test - public void testAdvancementsManagerNoFile() throws Exception { + void testAdvancementsManagerNoFile() throws Exception { // Delete the advancements.yml file so the constructor logs an error. Do NOT tear // down the full mock infrastructure — we still need it for the second manager. deleteAll(dataFolder); @@ -142,7 +142,7 @@ public void testAdvancementsManagerNoFile() throws Exception { * @throws IOException */ @Test - public void testAdvancementsManager() throws IOException { + void testAdvancementsManager() { verify(addon).saveResource("advancements.yml", false); verify(addon, never()).logError(anyString()); } @@ -151,7 +151,7 @@ public void testAdvancementsManager() throws IOException { * Test method for {@link world.bentobox.boxed.AdvancementsManager#getIsland(world.bentobox.bentobox.database.objects.Island)}. */ @Test - public void testGetIsland() { + void testGetIsland() { @NonNull IslandAdvancements adv = am.getIsland(island); assertEquals("uniqueId", adv.getUniqueId()); @@ -165,7 +165,7 @@ public void testGetIsland() { * @throws IllegalAccessException */ @Test - public void testSaveIslandNotInCache() throws IllegalAccessException, InvocationTargetException, IntrospectionException { + void testSaveIslandNotInCache() { am.removeFromCache(island); am.saveIsland(island); verify(island, times(2)).getUniqueId(); // 2x @@ -178,7 +178,7 @@ public void testSaveIslandNotInCache() throws IllegalAccessException, Invocation * @throws IllegalAccessException */ @Test - public void testSaveIslandInCache() throws IllegalAccessException, InvocationTargetException, IntrospectionException { + void testSaveIslandInCache() { testGetIsland(); am.saveIsland(island); verify(island, times(3)).getUniqueId(); // 3x @@ -191,7 +191,7 @@ public void testSaveIslandInCache() throws IllegalAccessException, InvocationTar * @throws IllegalAccessException */ @Test - public void testSaveNothingToSave() throws IllegalAccessException, InvocationTargetException, IntrospectionException { + void testSaveNothingToSave() { am.removeFromCache(island); am.save(); verify(island).getUniqueId(); @@ -204,7 +204,7 @@ public void testSaveNothingToSave() throws IllegalAccessException, InvocationTar * @throws IllegalAccessException */ @Test - public void testSave() throws IllegalAccessException, InvocationTargetException, IntrospectionException { + void testSave() { testGetIsland(); am.save(); verify(island).getUniqueId(); @@ -214,7 +214,7 @@ public void testSave() throws IllegalAccessException, InvocationTargetException, * Test method for {@link world.bentobox.boxed.AdvancementsManager#addAdvancement(world.bentobox.bentobox.database.objects.Island, java.lang.String)}. */ @Test - public void testAddAdvancementIslandString() { + void testAddAdvancementIslandString() { assertTrue(am.addAdvancement(island, "advancement")); assertFalse(am.addAdvancement(island, "advancement")); // Second time should fail } @@ -223,7 +223,7 @@ public void testAddAdvancementIslandString() { * Test method for {@link world.bentobox.boxed.AdvancementsManager#removeAdvancement(world.bentobox.bentobox.database.objects.Island, java.lang.String)}. */ @Test - public void testRemoveAdvancement() { + void testRemoveAdvancement() { assertTrue(am.addAdvancement(island, "advancement")); am.removeAdvancement(island, "advancement"); assertTrue(am.addAdvancement(island, "advancement")); // Should work because it was removed @@ -233,7 +233,7 @@ public void testRemoveAdvancement() { * Test method for {@link world.bentobox.boxed.AdvancementsManager#hasAdvancement(world.bentobox.bentobox.database.objects.Island, java.lang.String)}. */ @Test - public void testHasAdvancement() { + void testHasAdvancement() { assertFalse(am.hasAdvancement(island, "advancement")); am.addAdvancement(island, "advancement"); assertTrue(am.hasAdvancement(island, "advancement")); @@ -243,7 +243,7 @@ public void testHasAdvancement() { * Test method for {@link world.bentobox.boxed.AdvancementsManager#checkIslandSize(world.bentobox.bentobox.database.objects.Island)}. */ @Test - public void testCheckIslandSize() { + void testCheckIslandSize() { // Island protection size is set to 5, but after checking, the size is reduced by 4 assertEquals(-4, am.checkIslandSize(island)); } @@ -252,7 +252,7 @@ public void testCheckIslandSize() { * Test method for {@link world.bentobox.boxed.AdvancementsManager#addAdvancement(org.bukkit.entity.Player, org.bukkit.advancement.Advancement)}. */ @Test - public void testAddAdvancementPlayerAdvancementWrongWorld() { + void testAddAdvancementPlayerAdvancementWrongWorld() { when(addon.inWorld(world)).thenReturn(false); assertEquals(0, am.addAdvancement(player, advancement)); } @@ -261,7 +261,7 @@ public void testAddAdvancementPlayerAdvancementWrongWorld() { * Test method for {@link world.bentobox.boxed.AdvancementsManager#addAdvancement(org.bukkit.entity.Player, org.bukkit.advancement.Advancement)}. */ @Test - public void testAddAdvancementPlayerAdvancement() { + void testAddAdvancementPlayerAdvancement() { assertEquals(9, am.addAdvancement(player, advancement)); verify(island).setProtectionRange(14); // (9 + 5) } @@ -271,7 +271,7 @@ public void testAddAdvancementPlayerAdvancement() { * A null display means the advancement cannot be scored automatically. */ @Test - public void testAddAdvancementPlayerAdvancementZeroScore() { + void testAddAdvancementPlayerAdvancementZeroScore() { when(advancement.getDisplay()).thenReturn(null); assertEquals(0, am.addAdvancement(player, advancement)); verify(island, never()).setProtectionRange(org.mockito.ArgumentMatchers.anyInt()); @@ -281,7 +281,7 @@ public void testAddAdvancementPlayerAdvancementZeroScore() { * Test method for {@link world.bentobox.boxed.AdvancementsManager#getScore(java.lang.String)}. */ @Test - public void testGetScoreString() { + void testGetScoreString() { assertEquals(9, am.getScore("adventure/lightning_rod_with_villager_no_fire")); } @@ -289,7 +289,7 @@ public void testGetScoreString() { * Test method for {@link world.bentobox.boxed.AdvancementsManager#getScore(org.bukkit.advancement.Advancement)}. */ @Test - public void testGetScoreAdvancement() { + void testGetScoreAdvancement() { assertEquals(9, am.getScore(advancement)); } @@ -298,7 +298,7 @@ public void testGetScoreAdvancement() { * Root advancements fall back to settings.default-root-increase (0 in the shipped config). */ @Test - public void testGetScoreAdvancementRoot() { + void testGetScoreAdvancementRoot() { when(advancement.getKey()).thenReturn(NamespacedKey.fromString("story/root")); assertEquals(0, am.getScore(advancement)); } @@ -308,7 +308,7 @@ public void testGetScoreAdvancementRoot() { * Recipe advancements always score settings.unknown-recipe-increase (0 in the shipped config). */ @Test - public void testGetScoreAdvancementRecipe() { + void testGetScoreAdvancementRecipe() { when(advancement.getKey()).thenReturn(NamespacedKey.fromString("recipes/brewing/blaze_powder")); assertEquals(0, am.getScore(advancement)); } @@ -318,7 +318,7 @@ public void testGetScoreAdvancementRecipe() { * No island for this player means no expansion and a zero score. */ @Test - public void testAddAdvancementPlayerAdvancementNullIsland() { + void testAddAdvancementPlayerAdvancementNullIsland() { when(im.getIsland(world, player.getUniqueId())).thenReturn(null); assertEquals(0, am.addAdvancement(player, advancement)); verify(island, never()).setProtectionRange(org.mockito.ArgumentMatchers.anyInt()); @@ -329,7 +329,7 @@ public void testAddAdvancementPlayerAdvancementNullIsland() { * Visitors (rank below MEMBER_RANK) cannot expand the island. */ @Test - public void testAddAdvancementPlayerAdvancementVisitorRank() { + void testAddAdvancementPlayerAdvancementVisitorRank() { when(island.getRank(player.getUniqueId())).thenReturn(RanksManager.VISITOR_RANK); assertEquals(0, am.addAdvancement(player, advancement)); verify(island, never()).setProtectionRange(org.mockito.ArgumentMatchers.anyInt()); @@ -340,7 +340,7 @@ public void testAddAdvancementPlayerAdvancementVisitorRank() { * An advancement that's already been recorded on the island cannot grant a second expansion. */ @Test - public void testAddAdvancementPlayerAdvancementAlreadyHas() { + void testAddAdvancementPlayerAdvancementAlreadyHas() { // Seed the island with the exact same namespaced key the manager will try to record. am.addAdvancement(island, advancement.getKey().toString()); assertEquals(0, am.addAdvancement(player, advancement)); @@ -351,7 +351,7 @@ public void testAddAdvancementPlayerAdvancementAlreadyHas() { * Positive diff case: one scoring advancement grows a size-1 island to size 10. */ @Test - public void testCheckIslandSizePositiveDiff() { + void testCheckIslandSizePositiveDiff() { when(island.getProtectionRange()).thenReturn(1); am.addAdvancement(island, "adventure/honey_block_slide"); assertEquals(9, am.checkIslandSize(island)); diff --git a/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java b/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java index e6815b3..3eb15ec 100644 --- a/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java +++ b/src/test/java/world/bentobox/boxed/PlaceholdersManagerTest.java @@ -19,7 +19,7 @@ /** * @author tastybento */ -public class PlaceholdersManagerTest extends CommonTestSetup { +class PlaceholdersManagerTest extends CommonTestSetup { @Mock private Boxed addon; @@ -61,47 +61,47 @@ public void tearDown() throws Exception { } @Test - public void testGetCountNullUser() { + void testGetCountNullUser() { assertEquals("", phm.getCount(null)); } @Test - public void testGetCountNullUuid() { + void testGetCountNullUuid() { when(user.getUniqueId()).thenReturn(null); assertEquals("", phm.getCount(user)); } @Test - public void testGetCountNoIsland() { + void testGetCountNoIsland() { when(im.getIsland(world, user)).thenReturn(null); assertEquals("", phm.getCount(user)); } @Test - public void testGetCountReturnsAdvancementCount() { + void testGetCountReturnsAdvancementCount() { when(im.getIsland(world, user)).thenReturn(island); assertEquals("3", phm.getCount(user)); } @Test - public void testGetCountByLocationNullUser() { + void testGetCountByLocationNullUser() { assertEquals("", phm.getCountByLocation(null)); } @Test - public void testGetCountByLocationNullLocation() { + void testGetCountByLocationNullLocation() { when(user.getLocation()).thenReturn(null); assertEquals("", phm.getCountByLocation(user)); } @Test - public void testGetCountByLocationNoIslandAtLocation() { + void testGetCountByLocationNoIslandAtLocation() { when(im.getIslandAt(userLocation)).thenReturn(Optional.empty()); assertEquals("", phm.getCountByLocation(user)); } @Test - public void testGetCountByLocationReturnsAdvancementCount() { + void testGetCountByLocationReturnsAdvancementCount() { when(im.getIslandAt(userLocation)).thenReturn(Optional.of(island)); assertEquals("3", phm.getCountByLocation(user)); } diff --git a/src/test/java/world/bentobox/boxed/SettingsTest.java b/src/test/java/world/bentobox/boxed/SettingsTest.java index 482ac69..fb8aca4 100644 --- a/src/test/java/world/bentobox/boxed/SettingsTest.java +++ b/src/test/java/world/bentobox/boxed/SettingsTest.java @@ -20,7 +20,7 @@ * @author tastybento * */ -public class SettingsTest extends CommonTestSetup { +class SettingsTest extends CommonTestSetup { Settings s; @@ -40,7 +40,7 @@ public void tearDown() throws Exception { * Test method for {@link world.bentobox.boxed.Settings#setFriendlyName(java.lang.String)}. */ @Test - public void testSetFriendlyName() { + void testSetFriendlyName() { s.setFriendlyName("name"); assertEquals("name", s.getFriendlyName()); } @@ -49,7 +49,7 @@ public void testSetFriendlyName() { * Test method for {@link world.bentobox.boxed.Settings#setWorldName(java.lang.String)}. */ @Test - public void testSetWorldName() { + void testSetWorldName() { s.setWorldName("name"); assertEquals("name", s.getWorldName()); } @@ -58,7 +58,7 @@ public void testSetWorldName() { * Test method for {@link world.bentobox.boxed.Settings#setDifficulty(org.bukkit.Difficulty)}. */ @Test - public void testSetDifficulty() { + void testSetDifficulty() { s.setDifficulty(Difficulty.PEACEFUL); assertEquals(Difficulty.PEACEFUL, s.getDifficulty()); } @@ -67,7 +67,7 @@ public void testSetDifficulty() { * Test method for {@link world.bentobox.boxed.Settings#setIslandDistance(int)}. */ @Test - public void testSetIslandDistance() { + void testSetIslandDistance() { s.setIslandDistance(123); assertEquals(112, s.getIslandDistance()); verify(plugin).logWarning("Boxed: Area radius is not a factor of 16. Rounding to 112"); @@ -77,7 +77,7 @@ public void testSetIslandDistance() { * Test method for {@link world.bentobox.boxed.Settings#setIslandProtectionRange(int)}. */ @Test - public void testSetIslandProtectionRange() { + void testSetIslandProtectionRange() { s.setIslandProtectionRange(123); assertEquals(123, s.getIslandProtectionRange()); } @@ -86,7 +86,7 @@ public void testSetIslandProtectionRange() { * Test method for {@link world.bentobox.boxed.Settings#setIslandStartX(int)}. */ @Test - public void testSetIslandStartX() { + void testSetIslandStartX() { s.setIslandStartX(123); assertEquals(123, s.getIslandStartX()); } @@ -95,7 +95,7 @@ public void testSetIslandStartX() { * Test method for {@link world.bentobox.boxed.Settings#setIslandStartZ(int)}. */ @Test - public void testSetIslandStartZ() { + void testSetIslandStartZ() { s.setIslandStartZ(123); assertEquals(123, s.getIslandStartZ()); } @@ -104,7 +104,7 @@ public void testSetIslandStartZ() { * Test method for {@link world.bentobox.boxed.Settings#setMaxIslands(int)}. */ @Test - public void testSetMaxIslands() { + void testSetMaxIslands() { s.setMaxIslands(123); assertEquals(123, s.getMaxIslands()); } @@ -113,7 +113,7 @@ public void testSetMaxIslands() { * Test method for {@link world.bentobox.boxed.Settings#setNetherGenerate(boolean)}. */ @Test - public void testSetNetherGenerate() { + void testSetNetherGenerate() { s.setNetherGenerate(true); assertTrue(s.isNetherGenerate()); } @@ -122,7 +122,7 @@ public void testSetNetherGenerate() { * Test method for {@link world.bentobox.boxed.Settings#setEndGenerate(boolean)}. */ @Test - public void testSetEndGenerate() { + void testSetEndGenerate() { s.setEndGenerate(true); assertTrue(s.isEndGenerate()); } @@ -131,7 +131,7 @@ public void testSetEndGenerate() { * Test method for {@link world.bentobox.boxed.Settings#setRemoveMobsWhitelist(java.util.Set)}. */ @Test - public void testSetRemoveMobsWhitelist() { + void testSetRemoveMobsWhitelist() { Set wl = Collections.emptySet(); s.setRemoveMobsWhitelist(wl); assertEquals(wl, s.getRemoveMobsWhitelist()); @@ -141,7 +141,7 @@ public void testSetRemoveMobsWhitelist() { * Test method for {@link world.bentobox.boxed.Settings#setWorldFlags(java.util.Map)}. */ @Test - public void testSetWorldFlags() { + void testSetWorldFlags() { Map worldFlags = Collections.emptyMap(); s.setWorldFlags(worldFlags); assertEquals(worldFlags, s.getWorldFlags()); @@ -151,7 +151,7 @@ public void testSetWorldFlags() { * Test method for {@link world.bentobox.boxed.Settings#setHiddenFlags(java.util.List)}. */ @Test - public void testSetVisibleSettings() { + void testSetVisibleSettings() { List visibleSettings = Collections.emptyList(); s.setHiddenFlags(visibleSettings); assertEquals(visibleSettings, s.getHiddenFlags()); @@ -161,7 +161,7 @@ public void testSetVisibleSettings() { * Test method for {@link world.bentobox.boxed.Settings#setVisitorBannedCommands(java.util.List)}. */ @Test - public void testSetVisitorBannedCommands() { + void testSetVisitorBannedCommands() { List visitorBannedCommands = Collections.emptyList(); s.setVisitorBannedCommands(visitorBannedCommands); assertEquals(visitorBannedCommands, s.getVisitorBannedCommands()); @@ -171,7 +171,7 @@ public void testSetVisitorBannedCommands() { * Test method for {@link world.bentobox.boxed.Settings#setMaxTeamSize(int)}. */ @Test - public void testSetMaxTeamSize() { + void testSetMaxTeamSize() { s.setMaxTeamSize(123); assertEquals(123, s.getMaxTeamSize()); } @@ -180,7 +180,7 @@ public void testSetMaxTeamSize() { * Test method for {@link world.bentobox.boxed.Settings#setMaxHomes(int)}. */ @Test - public void testSetMaxHomes() { + void testSetMaxHomes() { s.setMaxHomes(123); assertEquals(123, s.getMaxHomes()); } @@ -189,7 +189,7 @@ public void testSetMaxHomes() { * Test method for {@link world.bentobox.boxed.Settings#setResetLimit(int)}. */ @Test - public void testSetResetLimit() { + void testSetResetLimit() { s.setResetLimit(123); assertEquals(123, s.getResetLimit()); } @@ -198,7 +198,7 @@ public void testSetResetLimit() { * Test method for {@link world.bentobox.boxed.Settings#setLeaversLoseReset(boolean)}. */ @Test - public void testSetLeaversLoseReset() { + void testSetLeaversLoseReset() { s.setLeaversLoseReset(true); assertTrue(s.isLeaversLoseReset()); } @@ -207,7 +207,7 @@ public void testSetLeaversLoseReset() { * Test method for {@link world.bentobox.boxed.Settings#setKickedKeepInventory(boolean)}. */ @Test - public void testSetKickedKeepInventory() { + void testSetKickedKeepInventory() { s.setKickedKeepInventory(true); assertTrue(s.isKickedKeepInventory()); } @@ -216,7 +216,7 @@ public void testSetKickedKeepInventory() { * Test method for {@link world.bentobox.boxed.Settings#setOnJoinResetMoney(boolean)}. */ @Test - public void testSetOnJoinResetMoney() { + void testSetOnJoinResetMoney() { s.setOnJoinResetMoney(true); assertTrue(s.isOnJoinResetMoney()); } @@ -225,7 +225,7 @@ public void testSetOnJoinResetMoney() { * Test method for {@link world.bentobox.boxed.Settings#setOnJoinResetInventory(boolean)}. */ @Test - public void testSetOnJoinResetInventory() { + void testSetOnJoinResetInventory() { s.setOnJoinResetInventory(true); assertTrue(s.isOnJoinResetInventory()); } @@ -234,7 +234,7 @@ public void testSetOnJoinResetInventory() { * Test method for {@link world.bentobox.boxed.Settings#setOnJoinResetEnderChest(boolean)}. */ @Test - public void testSetOnJoinResetEnderChest() { + void testSetOnJoinResetEnderChest() { s.setOnJoinResetEnderChest(true); assertTrue(s.isOnJoinResetEnderChest()); } @@ -243,7 +243,7 @@ public void testSetOnJoinResetEnderChest() { * Test method for {@link world.bentobox.boxed.Settings#setOnLeaveResetMoney(boolean)}. */ @Test - public void testSetOnLeaveResetMoney() { + void testSetOnLeaveResetMoney() { s.setOnLeaveResetMoney(true); assertTrue(s.isOnLeaveResetMoney()); } @@ -252,7 +252,7 @@ public void testSetOnLeaveResetMoney() { * Test method for {@link world.bentobox.boxed.Settings#setOnLeaveResetInventory(boolean)}. */ @Test - public void testSetOnLeaveResetInventory() { + void testSetOnLeaveResetInventory() { s.setOnLeaveResetInventory(true); assertTrue(s.isOnLeaveResetInventory()); } @@ -261,7 +261,7 @@ public void testSetOnLeaveResetInventory() { * Test method for {@link world.bentobox.boxed.Settings#setOnLeaveResetEnderChest(boolean)}. */ @Test - public void testSetOnLeaveResetEnderChest() { + void testSetOnLeaveResetEnderChest() { s.setOnLeaveResetEnderChest(true); assertTrue(s.isOnLeaveResetEnderChest()); } @@ -270,7 +270,7 @@ public void testSetOnLeaveResetEnderChest() { * Test method for {@link world.bentobox.boxed.Settings#setDeathsCounted(boolean)}. */ @Test - public void testSetDeathsCounted() { + void testSetDeathsCounted() { s.setDeathsCounted(true); assertTrue(s.isDeathsCounted()); } @@ -279,7 +279,7 @@ public void testSetDeathsCounted() { * Test method for {@link world.bentobox.boxed.Settings#setDeathsMax(int)}. */ @Test - public void testSetDeathsMax() { + void testSetDeathsMax() { s.setDeathsMax(123); assertEquals(123, s.getDeathsMax()); } @@ -288,7 +288,7 @@ public void testSetDeathsMax() { * Test method for {@link world.bentobox.boxed.Settings#setTeamJoinDeathReset(boolean)}. */ @Test - public void testSetTeamJoinDeathReset() { + void testSetTeamJoinDeathReset() { s.setTeamJoinDeathReset(true); assertTrue(s.isTeamJoinDeathReset()); } @@ -297,7 +297,7 @@ public void testSetTeamJoinDeathReset() { * Test method for {@link world.bentobox.boxed.Settings#setGeoLimitSettings(java.util.List)}. */ @Test - public void testSetGeoLimitSettings() { + void testSetGeoLimitSettings() { List geoLimitSettings = Collections.emptyList(); s.setGeoLimitSettings(geoLimitSettings); assertEquals(geoLimitSettings, s.getGeoLimitSettings()); @@ -307,7 +307,7 @@ public void testSetGeoLimitSettings() { * Test method for {@link world.bentobox.boxed.Settings#setIvSettings(java.util.List)}. */ @Test - public void testSetIvSettings() { + void testSetIvSettings() { List ivSettings = Collections.emptyList(); s.setIvSettings(ivSettings); assertEquals(ivSettings, s.getIvSettings()); @@ -317,7 +317,7 @@ public void testSetIvSettings() { * Test method for {@link world.bentobox.boxed.Settings#setAllowSetHomeInNether(boolean)}. */ @Test - public void testSetAllowSetHomeInNether() { + void testSetAllowSetHomeInNether() { s.setAllowSetHomeInNether(true); assertTrue(s.isAllowSetHomeInNether()); } @@ -326,7 +326,7 @@ public void testSetAllowSetHomeInNether() { * Test method for {@link world.bentobox.boxed.Settings#setAllowSetHomeInTheEnd(boolean)}. */ @Test - public void testSetAllowSetHomeInTheEnd() { + void testSetAllowSetHomeInTheEnd() { s.setAllowSetHomeInTheEnd(true); assertTrue(s.isAllowSetHomeInTheEnd()); } @@ -335,7 +335,7 @@ public void testSetAllowSetHomeInTheEnd() { * Test method for {@link world.bentobox.boxed.Settings#setRequireConfirmationToSetHomeInNether(boolean)}. */ @Test - public void testSetRequireConfirmationToSetHomeInNether() { + void testSetRequireConfirmationToSetHomeInNether() { s.setRequireConfirmationToSetHomeInNether(true); assertTrue(s.isRequireConfirmationToSetHomeInNether()); } @@ -344,7 +344,7 @@ public void testSetRequireConfirmationToSetHomeInNether() { * Test method for {@link world.bentobox.boxed.Settings#setRequireConfirmationToSetHomeInTheEnd(boolean)}. */ @Test - public void testSetRequireConfirmationToSetHomeInTheEnd() { + void testSetRequireConfirmationToSetHomeInTheEnd() { s.setRequireConfirmationToSetHomeInTheEnd(true); assertTrue(s.isRequireConfirmationToSetHomeInTheEnd()); } @@ -353,7 +353,7 @@ public void testSetRequireConfirmationToSetHomeInTheEnd() { * Test method for {@link world.bentobox.boxed.Settings#setResetEpoch(long)}. */ @Test - public void testSetResetEpoch() { + void testSetResetEpoch() { s.setResetEpoch(123); assertEquals(123, s.getResetEpoch()); } @@ -362,7 +362,7 @@ public void testSetResetEpoch() { * Test method for {@link world.bentobox.boxed.Settings#getPermissionPrefix()}. */ @Test - public void testGetPermissionPrefix() { + void testGetPermissionPrefix() { assertEquals("boxed", s.getPermissionPrefix()); } @@ -370,7 +370,7 @@ public void testGetPermissionPrefix() { * Test method for {@link world.bentobox.boxed.Settings#isWaterUnsafe()}. */ @Test - public void testIsWaterUnsafe() { + void testIsWaterUnsafe() { assertFalse(s.isWaterUnsafe()); } @@ -378,7 +378,7 @@ public void testIsWaterUnsafe() { * Test method for {@link world.bentobox.boxed.Settings#setBanLimit(int)}. */ @Test - public void testSetBanLimit() { + void testSetBanLimit() { s.setBanLimit(123); assertEquals(123, s.getBanLimit()); } @@ -387,7 +387,7 @@ public void testSetBanLimit() { * Test method for {@link Settings#getPlayerCommandAliases()} Command()}. */ @Test - public void testGetIslandCommand() { + void testGetIslandCommand() { s.setPlayerCommandAliases("island"); assertEquals("island", s.getPlayerCommandAliases()); } @@ -396,7 +396,7 @@ public void testGetIslandCommand() { * Test method for {@link Settings#getAdminCommandAliases()}. */ @Test - public void testGetAdminCommand() { + void testGetAdminCommand() { s.setAdminCommandAliases("admin"); assertEquals("admin", s.getAdminCommandAliases()); } diff --git a/src/test/java/world/bentobox/boxed/commands/AdminPlaceStructureCommandTest.java b/src/test/java/world/bentobox/boxed/commands/AdminPlaceStructureCommandTest.java new file mode 100644 index 0000000..1a64b38 --- /dev/null +++ b/src/test/java/world/bentobox/boxed/commands/AdminPlaceStructureCommandTest.java @@ -0,0 +1,186 @@ +package world.bentobox.boxed.commands; + +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 static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.structure.Structure; +import org.bukkit.structure.StructureManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.boxed.Boxed; +import world.bentobox.boxed.CommonTestSetup; + +/** + * Tests the argument validation ({@code canExecute}) and tab completion of + * {@link AdminPlaceStructureCommand}. + */ +class AdminPlaceStructureCommandTest extends CommonTestSetup { + + private AdminPlaceStructureCommand cmd; + private Boxed addon; + private User user; + + @BeforeEach + public void setUpCommand() { + addon = mock(Boxed.class); + + CompositeCommand parent = mock(CompositeCommand.class); + when(parent.getAddon()).thenReturn(addon); + when(parent.getTopLabel()).thenReturn("boxadmin"); + when(parent.getPermissionPrefix()).thenReturn(""); + when(parent.getWorld()).thenReturn(world); + when(parent.getSubCommands()).thenReturn(new HashMap<>()); + + cmd = new AdminPlaceStructureCommand(parent); + + // In the Boxed world by default + when(addon.inWorld(world)).thenReturn(true); + + // One known structure called "igloo" + StructureManager sm = Bukkit.getStructureManager(); + Map structures = new HashMap<>(); + structures.put(NamespacedKey.minecraft("igloo"), mock(Structure.class)); + when(sm.getStructures()).thenReturn(structures); + + user = mock(User.class); + when(user.getLocation()).thenReturn(location); + } + + @Test + void testSetup() { + assertEquals("place", cmd.getLabel()); + assertEquals("boxed.commands.boxadmin.place", cmd.getPermission()); + assertFalse(cmd.isOnlyPlayer()); + } + + @Test + void testUndoAlwaysAllowed() { + assertTrue(cmd.canExecute(user, "place", List.of("undo"))); + } + + @Test + void testWrongWorld() { + when(addon.inWorld(world)).thenReturn(false); + assertFalse(cmd.canExecute(user, "place", List.of("igloo"))); + verify(user).sendMessage("boxed.commands.boxadmin.place.wrong-world"); + } + + @Test + void testUnknownStructure() { + assertFalse(cmd.canExecute(user, "place", List.of("mansion"))); + verify(user).sendMessage("boxed.commands.boxadmin.place.unknown-structure"); + } + + @Test + void testStructureNameOnly() { + assertTrue(cmd.canExecute(user, "place", List.of("igloo"))); + } + + @Test + void testNonIntegerCoordinates() { + assertFalse(cmd.canExecute(user, "place", List.of("igloo", "x", "0", "0"))); + verify(user).sendMessage("boxed.commands.boxadmin.place.use-integers"); + } + + @Test + void testTildeCoordinates() { + assertTrue(cmd.canExecute(user, "place", List.of("igloo", "~", "~", "~"))); + } + + @Test + void testIntegerCoordinates() { + assertTrue(cmd.canExecute(user, "place", List.of("igloo", "10", "64", "-20"))); + } + + @Test + void testUnknownRotation() { + assertFalse(cmd.canExecute(user, "place", List.of("igloo", "~", "~", "~", "SPIN"))); + verify(user).sendMessage("boxed.commands.boxadmin.place.unknown-rotation"); + } + + @Test + void testValidRotation() { + assertTrue(cmd.canExecute(user, "place", List.of("igloo", "~", "~", "~", "CLOCKWISE_90"))); + } + + @Test + void testUnknownMirror() { + assertFalse(cmd.canExecute(user, "place", List.of("igloo", "~", "~", "~", "NONE", "FLIP"))); + verify(user).sendMessage("boxed.commands.boxadmin.place.unknown-mirror"); + } + + @Test + void testValidMirror() { + assertTrue(cmd.canExecute(user, "place", List.of("igloo", "~", "~", "~", "NONE", "LEFT_RIGHT"))); + } + + @Test + void testValidNoMobs() { + assertTrue(cmd.canExecute(user, "place", List.of("igloo", "~", "~", "~", "NONE", "LEFT_RIGHT", "NO_MOBS"))); + } + + @Test + void testUnknownTrailingArg() { + assertFalse(cmd.canExecute(user, "place", List.of("igloo", "~", "~", "~", "NONE", "LEFT_RIGHT", "MAYBE"))); + verify(user).sendMessage(eq("boxed.commands.boxadmin.place.unknown"), anyString(), anyString()); + } + + @Test + void testTooManyArgsRejected() { + // The size > 7 guard rejects an 8th argument. + assertFalse(cmd.canExecute(user, "place", + List.of("igloo", "~", "~", "~", "NONE", "LEFT_RIGHT", "NO_MOBS", "EXTRA"))); + } + + @Test + void testTabCompleteFirstArgOffersUndo() { + Optional> opt = cmd.tabComplete(user, "place", List.of("")); + assertTrue(opt.isPresent()); + assertTrue(opt.get().contains("undo")); + } + + @Test + void testTabCompleteSecondArgOffersStructures() { + Optional> opt = cmd.tabComplete(user, "place", List.of("place", "")); + assertTrue(opt.isPresent()); + assertTrue(opt.get().contains("igloo")); + } + + @Test + void testTabCompleteRotation() { + Optional> opt = cmd.tabComplete(user, "place", List.of("place", "igloo", "~", "~", "~", "")); + assertTrue(opt.isPresent()); + assertTrue(opt.get().contains("CLOCKWISE_90")); + } + + @Test + void testTabCompleteNoMobs() { + Optional> opt = cmd.tabComplete(user, "place", + List.of("place", "igloo", "~", "~", "~", "NONE", "LEFT_RIGHT", "")); + assertTrue(opt.isPresent()); + assertEquals(List.of("NO_MOBS"), opt.get()); + } + + @Test + void testNeverMessagesWhenValid() { + cmd.canExecute(user, "place", List.of("igloo")); + verify(user, never()).sendMessage(anyString()); + } +} diff --git a/src/test/java/world/bentobox/boxed/generators/biomes/CopyBiomeProviderTest.java b/src/test/java/world/bentobox/boxed/generators/biomes/CopyBiomeProviderTest.java new file mode 100644 index 0000000..2217520 --- /dev/null +++ b/src/test/java/world/bentobox/boxed/generators/biomes/CopyBiomeProviderTest.java @@ -0,0 +1,96 @@ +package world.bentobox.boxed.generators.biomes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.World.Environment; +import org.bukkit.block.Biome; +import org.bukkit.generator.WorldInfo; +import org.bukkit.util.Vector; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import world.bentobox.boxed.Boxed; +import world.bentobox.boxed.CommonTestSetup; +import world.bentobox.boxed.Settings; +import world.bentobox.boxed.WhiteBox; +import world.bentobox.boxed.generators.chunks.AbstractBoxedChunkGenerator; +import world.bentobox.boxed.generators.chunks.AbstractBoxedChunkGenerator.ChunkStore; + +/** + * Tests {@link AbstractCopyBiomeProvider#getBiome(WorldInfo, int, int, int)} via the + * concrete overworld {@link BoxedBiomeGenerator}. The provider looks up the biome that + * was captured from the seed world for the (wrapped) chunk and the in-chunk position. + */ +class CopyBiomeProviderTest extends CommonTestSetup { + + private BoxedBiomeGenerator gen; + private AbstractBoxedChunkGenerator chunkGen; + private WorldInfo worldInfo; + private Boxed addon; + + @BeforeEach + public void setUpProvider() { + addon = mock(Boxed.class); + Settings settings = mock(Settings.class); + when(addon.getSettings()).thenReturn(settings); + when(settings.getIslandDistance()).thenReturn(400); + + chunkGen = mock(AbstractBoxedChunkGenerator.class); + when(addon.getChunkGenerator(Environment.NORMAL)).thenReturn(chunkGen); + + worldInfo = mock(WorldInfo.class); + when(worldInfo.getEnvironment()).thenReturn(Environment.NORMAL); + + // 400 / 16 = 25 chunks half-width. repeatCalc needs size > 0. + WhiteBox.setInternalState(AbstractBoxedChunkGenerator.class, "size", 25); + + gen = new BoxedBiomeGenerator(addon); + } + + @Test + void testReturnsStoredBiome() { + // x=3,z=5 -> chunk 0,0 ; in-chunk position (3, y, 5) + Map biomes = new HashMap<>(); + biomes.put(new Vector(3, 64, 5), Biome.PLAINS); + when(chunkGen.getChunk(0, 0)).thenReturn(new ChunkStore(null, null, null, biomes)); + + assertEquals(Biome.PLAINS, gen.getBiome(worldInfo, 3, 64, 5)); + } + + @Test + void testReturnsDefaultBiomeWhenPositionNotStored() { + // Chunk exists but the requested position is not in the map -> default (OCEAN) + when(chunkGen.getChunk(0, 0)).thenReturn(new ChunkStore(null, null, null, new HashMap<>())); + + assertEquals(Biome.OCEAN, gen.getBiome(worldInfo, 3, 64, 5)); + } + + @Test + void testReturnsDefaultBiomeAndWarnsWhenChunkMissing() { + when(chunkGen.getChunk(0, 0)).thenReturn(null); + + assertEquals(Biome.OCEAN, gen.getBiome(worldInfo, 3, 64, 5)); + // The missing snapshot is logged as a warning + verify(plugin).logWarning(eq("Snapshot at 0 0 is not stored")); + } + + @Test + void testCoordinatesAreWrappedIntoSeedRegion() { + // x in a far chunk still resolves back into the stored region. + // x = 3 + 16*25 = 403 -> chunkX 25 -> repeatCalc(25)= -25 ... but getChunk is + // stubbed on the wrapped coord, so verify the wrapped lookup is used. + Map 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")); + } +}