diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java index c74349d20..f8b66f38e 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java @@ -6,6 +6,7 @@ import java.util.Objects; import org.bukkit.ChatColor; import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.events.island.IslandEvent; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -93,8 +94,18 @@ public class IslandSetnameCommand extends CompositeCommand { } // Everything's good! - Objects.requireNonNull(getIslands().getIsland(getWorld(), user)).setName(name); + Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), user)); + String previousName = island.getName(); + island.setName(name); user.sendMessage("commands.island.setname.success", TextVariables.NAME, name); + // Fire the IslandNameEvent + new IslandEvent.IslandEventBuilder() + .island(island) + .involvedPlayer(user.getUniqueId()) + .reason(IslandEvent.Reason.NAME) + .previousName(previousName) + .admin(false) + .build(); return true; } } diff --git a/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java b/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java index 701d553d3..45d190c2d 100644 --- a/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java +++ b/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java @@ -7,6 +7,7 @@ import org.bukkit.Location; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle; @@ -169,7 +170,12 @@ public class IslandEvent extends IslandBaseEvent { * Event that will fire any time a player's rank changes on an island. * @since 1.13.0 */ - RANK_CHANGE + RANK_CHANGE, + /** + * Event that will fire when an island is named or renamed + * @since 1.24.0 + */ + NAME } public static IslandEventBuilder builder() { @@ -213,6 +219,10 @@ public class IslandEvent extends IslandBaseEvent { * @since 1.13.0 */ private int newRank; + /** + * @since 1.24.0 Previous name of island + */ + private String previousName; public IslandEventBuilder island(Island island) { this.island = island; @@ -305,6 +315,16 @@ public class IslandEvent extends IslandBaseEvent { return this; } + /** + * Sets the previous name of the island + * @param previousName previous name. May be null. + * @since 1.24.0 + */ + public IslandEventBuilder previousName(@Nullable String previousName) { + this.previousName = previousName; + return this; + } + private IslandBaseEvent getEvent() { return switch (reason) { case EXPEL -> new IslandExpelEvent(island, player, admin, location); @@ -329,6 +349,7 @@ public class IslandEvent extends IslandBaseEvent { case RESERVED -> new IslandReservedEvent(island, player, admin, location); case RANK_CHANGE -> new IslandRankChangeEvent(island, player, admin, location, oldRank, newRank); case NEW_ISLAND -> new IslandNewIslandEvent(island, player, admin, location); + case NAME -> new IslandNameEvent(island, player, admin, location, previousName); default -> new IslandGeneralEvent(island, player, admin, location); }; } @@ -345,5 +366,6 @@ public class IslandEvent extends IslandBaseEvent { Bukkit.getPluginManager().callEvent(newEvent); return newEvent; } + } } diff --git a/src/main/java/world/bentobox/bentobox/api/events/island/IslandNameEvent.java b/src/main/java/world/bentobox/bentobox/api/events/island/IslandNameEvent.java new file mode 100644 index 000000000..81b02e1f2 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/events/island/IslandNameEvent.java @@ -0,0 +1,44 @@ +package world.bentobox.bentobox.api.events.island; + +import java.util.UUID; + +import org.bukkit.Location; +import org.bukkit.event.HandlerList; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.events.IslandBaseEvent; +import world.bentobox.bentobox.database.objects.Island; + +/** + * Fired when an a player names or renames an island. + * Cancellation has no effect. + */ +public class IslandNameEvent extends IslandBaseEvent { + + private final String previousName; + private static final HandlerList handlers = new HandlerList(); + + @Override + public @NonNull HandlerList getHandlers() { + return getHandlerList(); + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public IslandNameEvent(Island island, UUID player, boolean admin, Location location, @Nullable String previousName) { + // Final variables have to be declared in the constructor + super(island, player, admin, location); + this.previousName = previousName; + } + + /** + * @return the previous name of the island, if any. May be null if no name previously used. + */ + @Nullable + public String getPreviousNameIsland() { + return previousName; + } +} \ No newline at end of file diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommandTest.java new file mode 100644 index 000000000..bdc919f7d --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommandTest.java @@ -0,0 +1,281 @@ +/** + * + */ +package world.bentobox.bentobox.api.commands.island; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +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; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; +import org.eclipse.jdt.annotation.NonNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.LocalesManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.bentobox.managers.RanksManager; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Bukkit.class, BentoBox.class}) +public class IslandSetnameCommandTest { + + @Mock + private CompositeCommand ic; + private UUID uuid; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private PlayersManager pm; + @Mock + private Island island; + @Mock + private Addon addon; + + private IslandSetnameCommand isc; + @Mock + private @NonNull World world; + private RanksManager rm; + private Settings settings; + @Mock + private PluginManager pim; + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + User.setPlugin(plugin); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Settings + settings = new Settings(); + when(plugin.getSettings()).thenReturn(settings); + + // User + when(user.isOp()).thenReturn(false); + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + Player p = mock(Player.class); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + when(user.getDisplayName()).thenReturn("&Ctastybento"); + when(user.getPermissionValue(anyString(), anyInt())).thenReturn(-1); + when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); + + // Parent command has no aliases + when(ic.getSubCommandAliases()).thenReturn(new HashMap<>()); + when(ic.getWorld()).thenReturn(world); + + // IWM friendly name + IslandWorldManager iwm = mock(IslandWorldManager.class); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); + when(plugin.getIWM()).thenReturn(iwm); + + // Player has island to begin with + when(im.getIsland(world, user)).thenReturn(island); + when(plugin.getIslands()).thenReturn(im); + when(island.getName()).thenReturn("previous-name"); + + // Server and Plugin Manager for events + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + when(Bukkit.getPluginManager()).thenReturn(pim); + + // Locales + LocalesManager lm = mock(LocalesManager.class); + when(lm.get(any(), any())).thenAnswer(invocation -> invocation.getArgument(1, String.class)); + when(plugin.getLocalesManager()).thenReturn(lm); + PlaceholdersManager phm = mock(PlaceholdersManager.class); + when(phm.replacePlaceholders(any(), any())).thenAnswer(invocation -> invocation.getArgument(1, String.class)); + // Placeholder manager + when(plugin.getPlaceholdersManager()).thenReturn(phm); + + // Ranks Manager + rm = new RanksManager(); + when(plugin.getRanksManager()).thenReturn(rm); + + + // Test + isc = new IslandSetnameCommand(ic); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#IslandSetnameCommand(world.bentobox.bentobox.api.commands.CompositeCommand)}. + */ + @Test + public void testIslandSetnameCommand() { + assertEquals("setname", isc.getLabel()); + + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#setup()}. + */ + @Test + public void testSetup() { + assertTrue(isc.isOnlyPlayer()); + assertEquals("commands.island.setname.parameters", isc.getParameters()); + assertEquals("commands.island.setname.description", isc.getDescription()); + assertEquals("island.name", isc.getPermission()); + + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandNoArgs() { + assertFalse(isc.canExecute(user, isc.getLabel(), new ArrayList<>())); + verify(user).sendMessage("commands.help.header", "[label]", "BSkyBlock"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandNoIsland() { + when(im.getIsland(world, user)).thenReturn(null); + assertFalse(isc.canExecute(user, isc.getLabel(), List.of("name"))); + verify(user).sendMessage("general.errors.no-island"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testTooLowRank() { + when(island.getRank(any(User.class))).thenReturn(RanksManager.MEMBER_RANK); + when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK); + assertFalse(isc.canExecute(user, isc.getLabel(), List.of("name"))); + verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, "ranks.member"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandNameTooShort() { + assertFalse(isc.canExecute(user, isc.getLabel(), List.of("x"))); + verify(user).sendMessage("commands.island.setname.name-too-short", TextVariables.NUMBER, "4"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandNameOnlyColors() { + assertFalse(isc.canExecute(user, isc.getLabel(), List.of("§b§c§d§e"))); + verify(user).sendMessage("commands.island.setname.name-too-short", TextVariables.NUMBER, "4"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandNameTooLong() { + assertFalse(isc.canExecute(user, isc.getLabel(), List.of("This is a very long name that is not allowed and will have to be prevented"))); + verify(user).sendMessage("commands.island.setname.name-too-long", TextVariables.NUMBER, "20"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandNameNotUnique() { + settings.setNameUniqueness(true); + when(im.nameExists(eq(world), anyString())).thenReturn(true); + assertFalse(isc.canExecute(user, isc.getLabel(), List.of("name2"))); + verify(user).sendMessage("commands.island.setname.name-already-exists"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandNameApplyColors() { + when(user.hasPermission(anyString())).thenReturn(true); + settings.setNameUniqueness(true); + when(im.nameExists(world, "name§b")).thenReturn(true); + assertFalse(isc.canExecute(user, isc.getLabel(), List.of("name&b"))); + verify(user).sendMessage("commands.island.setname.name-already-exists"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testIslandSetnameCommandAllOK() { + assertTrue(isc.canExecute(user, isc.getLabel(), List.of("name-okay"))); + verify(user, never()).sendMessage(anyString()); + } + + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.island.IslandSetnameCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfString() { + when(user.hasPermission(anyString())).thenReturn(true); + assertTrue(isc.execute(user, isc.getLabel(), List.of("name-okay"))); + verify(island).setName("name-okay"); + verify(user).sendMessage("commands.island.setname.success", TextVariables.NAME, "name-okay"); + verify(pim, times(2)).callEvent(any()); + } + +}