diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java index 89c263e1f..7b14acd12 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java @@ -2,6 +2,7 @@ package com.velocitypowered.proxy.command; import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; +import static com.mojang.brigadier.arguments.StringArgumentType.word; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -91,8 +92,7 @@ public class BrigadierCommandTests extends CommandTestSuite { assertEquals(2, context.getNodes().size()); callCount.incrementAndGet(); return 1; - }) - ) + })) .build(); manager.register(new BrigadierCommand(node)); @@ -113,12 +113,73 @@ public class BrigadierCommandTests extends CommandTestSuite { .requires(source -> { callCount.incrementAndGet(); return false; - }) - ) + })) .build(); manager.register(new BrigadierCommand(node)); assertHandled("hello world"); assertEquals(1, callCount.get()); } + + // Suggestions + + @Test + void testArgumentSuggestions() { + final var node = LiteralArgumentBuilder + .literal("hello") + .then(RequiredArgumentBuilder + .argument("argument", word()) + .suggests((context, builder) -> builder + .suggest("foo") + .suggest("bar") + .suggest("baz") + .buildFuture())) + .build(); + manager.register(new BrigadierCommand(node)); + + assertSuggestions("hello ", "bar", "baz", "foo"); + assertSuggestions("hello ba", "bar", "baz", "foo"); + assertSuggestions("hello bar", "baz", "foo"); + } + + // The following 2 tests ensure we strictly follow Brigadier's behavior, even + // if it doesn't make much sense. + + @Test + void testSuggestsEvenIfImpermissible() { + final var node = LiteralArgumentBuilder + .literal("parent") + .then(LiteralArgumentBuilder + .literal("child") + .requiresWithContext((context, reader) -> fail())) + .build(); + manager.register(new BrigadierCommand(node)); + + assertSuggestions("parent ", "child"); + assertSuggestions("parent chi", "child"); + } + + @Test + void testDoesNotSuggestIfImpermissibleDuringParse() { + final var callCount = new AtomicInteger(); + + final var node = LiteralArgumentBuilder + .literal("parent") + .then(LiteralArgumentBuilder + .literal("child") + .requiresWithContext((context, reader) -> { + // CommandDispatcher#parseNodes checks whether the child node can be added + // to the context object. CommandDispatcher#getCompletionSuggestions then + // considers a suggestion context with "parent" as the parent, and considers + // the suggestions of relevant children, which includes "child". + assertEquals(2, context.getNodes().size()); + callCount.incrementAndGet(); + return false; + })) + .build(); + manager.register(new BrigadierCommand(node)); + + assertSuggestions("parent child"); + assertEquals(1, callCount.get()); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java index 0d6570de4..d6dbbbeb2 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java @@ -157,10 +157,12 @@ public class CommandManagerTests { assertTrue(manager.hasCommand("foo")); } - static class DummyCommand implements SimpleCommand { + static final class DummyCommand implements SimpleCommand { static final DummyCommand INSTANCE = new DummyCommand(); + private DummyCommand() {} + @Override public void execute(final Invocation invocation) { fail(); diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java index 5e37be8b9..ca8f3634b 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java @@ -3,7 +3,10 @@ package com.velocitypowered.proxy.command; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import com.google.common.collect.ImmutableList; import com.velocitypowered.api.command.RawCommand; +import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; @@ -90,4 +93,103 @@ public class RawCommandTests extends CommandTestSuite { assertHandled("color red"); assertEquals(1, callCount.get()); } + + // Suggestions + + @Test + void testSuggestsArgumentsAfterAlias() { + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + assertEquals("hello", invocation.alias()); + assertEquals("", invocation.arguments()); + return ImmutableList.of("world", "people"); // ensures we don't mutate the user's list + } + }); + + assertSuggestions("hello ", "people", "world"); // in alphabetical order + } + + @Test + void testSuggestsArgumentsAfterPartialArguments() { + final var meta = manager.metaBuilder("numbers").build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + assertEquals("12345678", invocation.arguments()); + return Collections.singletonList("9"); + } + }); + + assertSuggestions("numbers 12345678", "9"); + } + + @Test + void testDoesNotSuggestFirstArgumentIfImpermissibleAlias() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + assertEquals("hello", invocation.alias()); + assertEquals("", invocation.arguments()); + callCount.incrementAndGet(); + return false; + } + + @Override + public List suggest(final Invocation invocation) { + return fail(); + } + }); + + assertSuggestions("hello "); + assertEquals(1, callCount.get()); + } + + @Test + void testDoesNotSuggestArgumentsAfterPartialImpermissibleArguments() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("foo").build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + assertEquals("foo", invocation.alias()); + assertEquals("bar baz ", invocation.arguments()); + callCount.incrementAndGet(); + return false; + } + + @Override + public List suggest(final Invocation invocation) { + return fail(); + } + }); + + assertSuggestions("foo bar baz "); + assertEquals(1, callCount.get()); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java index f52c8cf3d..3cead1f69 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java @@ -4,7 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import com.google.common.collect.ImmutableList; import com.velocitypowered.api.command.SimpleCommand; +import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; @@ -91,4 +94,103 @@ public class SimpleCommandTests extends CommandTestSuite { assertHandled("color red"); assertEquals(1, callCount.get()); } + + // Suggestions + + @Test + void testSuggestsArgumentsAfterAlias() { + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + assertEquals("hello", invocation.alias()); + assertArrayEquals(new String[0], invocation.arguments()); + return ImmutableList.of("world", "people"); // ensures we don't mutate the user's list + } + }); + + assertSuggestions("hello ", "people", "world"); // in alphabetical order + } + + @Test + void testSuggestsArgumentsAfterPartialArguments() { + final var meta = manager.metaBuilder("numbers").build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + assertArrayEquals(new String[] { "12345678" }, invocation.arguments()); + return Collections.singletonList("9"); + } + }); + + assertSuggestions("numbers 12345678", "9"); + } + + @Test + void testDoesNotSuggestFirstArgumentIfImpermissibleAlias() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + assertEquals("hello", invocation.alias()); + assertArrayEquals(new String[0], invocation.arguments()); + callCount.incrementAndGet(); + return false; + } + + @Override + public List suggest(final Invocation invocation) { + return fail(); + } + }); + + assertSuggestions("hello "); + assertEquals(1, callCount.get()); + } + + @Test + void testDoesNotSuggestArgumentsAfterPartialImpermissibleArguments() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("foo").build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + assertEquals("foo", invocation.alias()); + assertArrayEquals(new String[] { "bar", "baz", "" }, invocation.arguments()); + callCount.incrementAndGet(); + return false; + } + + @Override + public List suggest(final Invocation invocation) { + return fail(); + } + }); + + assertSuggestions("foo bar baz "); + assertEquals(1, callCount.get()); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java index bc4b9e10d..1cb5d0551 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java @@ -17,6 +17,127 @@ package com.velocitypowered.proxy.command; -public class SuggestionsProviderTests { - // TODO +import static com.mojang.brigadier.arguments.StringArgumentType.word; +import static org.junit.jupiter.api.Assertions.fail; + +import com.google.common.collect.ImmutableList; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.RawCommand; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link Command} implementation-independent suggestion methods of + * {@link SuggestionsProvider}. + */ +public class SuggestionsProviderTests extends CommandTestSuite { + + @Test + void testSuggestsAliasForEmptyInput() { + final var meta = manager.metaBuilder("foo") + .aliases("bar", "baz") + .build(); + manager.register(meta, NoSuggestionsCommand.INSTANCE); + + assertSuggestions("", "bar", "baz", "foo"); + } + + @Test + void testDoesNotSuggestForLeadingWhitespace() { + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, NoSuggestionsCommand.INSTANCE); + + assertSuggestions(" "); + } + + @Test + void testSuggestsAliasesForPartialAlias() { + final var meta = manager.metaBuilder("foo") + .aliases("bar", "baz") + .build(); + manager.register(meta, NoSuggestionsCommand.INSTANCE); + + assertSuggestions("ba", "bar", "baz"); + assertSuggestions("fo", "foo"); + assertSuggestions("bar"); + } + + @Test + void testSuggestsHintLiteral() { + final var hint = LiteralArgumentBuilder + .literal("hint") + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, NoSuggestionsCommand.INSTANCE); + + assertSuggestions("hello ", "hint"); + assertSuggestions("hello hin", "hint"); + assertSuggestions("hello hint"); + } + + @Test + void testSuggestsHintCustomSuggestions() { + final var hint = RequiredArgumentBuilder + .argument("hint", word()) + .suggests((context, builder) -> builder + .suggest("one") + .suggest("two") + .suggest("three") + .buildFuture()) + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, NoSuggestionsCommand.INSTANCE); + + assertSuggestions("hello ", "one", "three", "two"); + assertSuggestions("hello two", "one", "three"); + } + + @Test + void testSuggestsMergesArgumentsSuggestionsWithHintSuggestions() { + final var hint = LiteralArgumentBuilder + .literal("bar") + .build(); + final var meta = manager.metaBuilder("foo") + .hint(hint) + .build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return ImmutableList.of("baz", "qux"); + } + }); + + // TODO Fix merging + assertSuggestions("hello ", "bar", "baz", "qux"); + assertSuggestions("hello bar", "baz", "qux"); + } + + static final class NoSuggestionsCommand implements RawCommand { + + static final NoSuggestionsCommand INSTANCE = new NoSuggestionsCommand(); + + private NoSuggestionsCommand() {} + + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return ImmutableList.of(); + } + } }