diff --git a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java index 7c8d74560..b10093403 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java @@ -53,6 +53,12 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi */ private boolean configurableRankCommand = false; + /** + * True if command is hidden from help and tab complete + * @since 1.13.0 + */ + private boolean hidden = false; + /** * The parameters string for this command. It is the commands followed by a locale reference. */ @@ -616,7 +622,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi } /** - * Returns a list containing all the labels of the subcommands for the provided CompositeCommand. + * Returns a list containing all the labels of the subcommands for the provided CompositeCommand excluding any hidden commands * @param sender the CommandSender * @param command the CompositeCommand to get the subcommands from * @return a list of subcommands labels or an empty list. @@ -624,6 +630,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi @NonNull private List getSubCommandLabels(@NonNull CommandSender sender, @NonNull CompositeCommand command) { return command.getSubCommands().values().stream() + .filter(cmd -> !cmd.isHidden()) .filter(cmd -> !cmd.isOnlyPlayer() || sender.isOp() || (sender instanceof Player && cmd.getPermission() != null && (cmd.getPermission().isEmpty() || sender.hasPermission(cmd.getPermission()))) ) .map(CompositeCommand::getLabel).collect(Collectors.toList()); } @@ -775,4 +782,22 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi this.configurableRankCommand = true; } + /** + * Checks if a command is hidden + * @return the hidden + * @since 1.13.0 + */ + public boolean isHidden() { + return hidden; + } + + /** + * Sets a command and all its help and tab complete as hidden + * @param hidden whether command is hidden or not + * @since 1.13.0 + */ + public void setHidden(boolean hidden) { + this.hidden = hidden; + } + } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/DefaultHelpCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/DefaultHelpCommand.java index 654bd247e..8c6f1508e 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/DefaultHelpCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/DefaultHelpCommand.java @@ -39,6 +39,9 @@ public class DefaultHelpCommand extends CompositeCommand { @Override public boolean execute(User user, String label, List args) { + // If command is hidden, do not show anything + if (parent.isHidden()) return true; + // Show default help int depth = 0; if (args.size() == 1) { if (NumberUtils.isDigits(args.get(0))) { diff --git a/src/test/java/world/bentobox/bentobox/api/commands/HiddenCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/HiddenCommandTest.java new file mode 100644 index 000000000..f8c36e630 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/api/commands/HiddenCommandTest.java @@ -0,0 +1,201 @@ +package world.bentobox.bentobox.api.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.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.List; + +import org.bukkit.command.CommandSender; +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.mockito.stubbing.Answer; +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.api.events.command.CommandEvent; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.managers.CommandsManager; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({BentoBox.class, CommandEvent.class}) +public class HiddenCommandTest { + + @Mock + private BentoBox plugin; + + @Mock + private User user; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + // Translation + when(user.getTranslation(anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + + } + + @After + public void tearDown() { + Mockito.framework().clearInlineMocks(); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.CompositeCommand#tabComplete(org.bukkit.command.CommandSender, java.lang.String, java.lang.String[])}. + */ + @Test + public void testTabCompleteCommandSenderStringStringArrayVisible() { + TopLevelCommand tlc = new TopLevelCommand(); + CommandSender sender = mock(CommandSender.class); + String[] args = {"v"}; + List opList = tlc.tabComplete(sender, "top", args); + assertFalse(opList.isEmpty()); + assertEquals("visible", opList.get(0)); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.CompositeCommand#tabComplete(org.bukkit.command.CommandSender, java.lang.String, java.lang.String[])}. + */ + @Test + public void testTabCompleteCommandSenderStringStringArrayHidden() { + TopLevelCommand tlc = new TopLevelCommand(); + CommandSender sender = mock(CommandSender.class); + String[] args = {"h"}; + List opList = tlc.tabComplete(sender, "top", args); + assertEquals(1, opList.size()); + assertEquals("help", opList.get(0)); // Only help + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.CompositeCommand#tabComplete(org.bukkit.command.CommandSender, java.lang.String, java.lang.String[])}. + */ + @Test + public void testTabCompleteCommandSenderStringStringArrayInvisible() { + TopLevelCommand tlc = new TopLevelCommand(); + CommandSender sender = mock(CommandSender.class); + String[] args = {"i"}; + List opList = tlc.tabComplete(sender, "top", args); + assertTrue(opList.isEmpty()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.CompositeCommand#showHelp(world.bentobox.bentobox.api.commands.CompositeCommand, world.bentobox.bentobox.api.user.User)}. + */ + @Test + public void testShowHelp() { + // Only the visible command should show in help + TopLevelCommand tlc = new TopLevelCommand(); + tlc.showHelp(tlc, user); + verify(user).sendMessage(eq("commands.help.header"), eq(TextVariables.LABEL), eq("commands.help.console")); + verify(user).sendMessage(eq("commands.help.syntax-no-parameters"), eq("[usage]"), eq("/top"), eq(TextVariables.DESCRIPTION), eq("top.description")); + verify(user).sendMessage(eq("commands.help.syntax-no-parameters"), eq("[usage]"), eq("/top visible"), eq(TextVariables.DESCRIPTION), eq("visible.description")); + verify(user, never()).sendMessage(eq("commands.help.syntax-no-parameters"), eq("[usage]"), eq("/top hidden"), eq(TextVariables.DESCRIPTION), anyString()); + verify(user, never()).sendMessage(eq("commands.help.syntax-no-parameters"), eq("[usage]"), eq("/top hidden2"), eq(TextVariables.DESCRIPTION), anyString()); + verify(user).sendMessage(eq("commands.help.end")); + + } + + class TopLevelCommand extends CompositeCommand { + + public TopLevelCommand() { + super("top"); + } + + @Override + public void setup() { + this.setParametersHelp("top.parameters"); + this.setDescription("top.description"); + new VisibleCommand(this); + new HiddenCommand(this); + new Hidden2Command(this); + } + + @Override + public boolean execute(User user, String label, List args) { + return true; + } + + } + + class VisibleCommand extends CompositeCommand { + + public VisibleCommand(CompositeCommand parent) { + super(parent, "visible"); + } + + @Override + public void setup() { + this.setParametersHelp("visible.parameters"); + this.setDescription("visible.description"); + } + + @Override + public boolean execute(User user, String label, List args) { + return true; + } + + } + + class HiddenCommand extends CompositeCommand { + + public HiddenCommand(CompositeCommand parent) { + super(parent, "hidden"); + } + + @Override + public void setup() { + this.setHidden(true); + } + + @Override + public boolean execute(User user, String label, List args) { + return true; + } + + } + + class Hidden2Command extends CompositeCommand { + + public Hidden2Command(CompositeCommand parent) { + super(parent, "invisible"); + } + + @Override + public void setup() { + this.setHidden(true); + this.setParametersHelp("invisible.parameters"); + this.setDescription("invisible.description"); + + } + + @Override + public boolean execute(User user, String label, List args) { + return true; + } + + } +}