diff --git a/.gitignore b/.gitignore
index c681da171e..ae37c0068e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,10 @@ target
build
**/logs/*.log
**/logs/*.log.gz
+logs/latest.log
+**/logs/*
+commandapi-plugin/dependency-reduced-pom.xml
+commandapi-shade/dependency-reduced-pom.xml
.vscode
.DS_Store
docssrc/src/.markdownlint-cli2.yaml
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/pom.xml
new file mode 100644
index 0000000000..129f76748c
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+ commandapi-bukkit
+ dev.jorel
+ 9.0.4
+
+
+ commandapi-bukkit-test-toolkit
+
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+ minecraft-libraries
+ https://libraries.minecraft.net
+
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.20.1-R0.1-SNAPSHOT
+ provided
+
+
+
+
+ com.mojang
+ brigadier
+ 1.0.17
+ provided
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.8.2
+ provided
+
+
+
+
+ com.github.seeseemelk
+ MockBukkit-v1.19
+ 2.29.0
+ provided
+
+
+
+
+ dev.jorel
+ commandapi-bukkit-test-impl-1.20
+ ${project.version}
+ provided
+
+
+
+
+ dev.jorel
+ commandapi-bukkit-shade
+ ${project.version}
+
+
+
\ No newline at end of file
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/CommandAPIVersionHandler.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/CommandAPIVersionHandler.java
new file mode 100644
index 0000000000..1d776c899b
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/CommandAPIVersionHandler.java
@@ -0,0 +1,40 @@
+package dev.jorel.commandapi;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import dev.jorel.commandapi.nms.NMS_1_19_4_R3;
+import dev.jorel.commandapi.nms.NMS_1_20_R1;
+import dev.jorel.commandapi.test.MockNMS;
+import dev.jorel.commandapi.test.Version;
+
+/**
+ * This file handles loading the correct platform implementation. The CommandAPIVersionHandler
+ * file within the commandapi-core module is NOT used at run time. Instead, the platform modules
+ * replace this class with their own version that handles loads the correct class for their version
+ */
+public interface CommandAPIVersionHandler {
+
+ static AtomicReference version = new AtomicReference<>();
+
+ public static void setVersion(Version version) {
+ CommandAPIVersionHandler.version.set(version);
+ }
+
+ // The only important thing we care about.
+ static CommandAPIPlatform, ?, ?> getPlatform() {
+ if(CommandAPIVersionHandler.version.get() == null) {
+ System.out.println("Using default version 1.19.4");
+ return new MockNMS(new NMS_1_19_4_R3());
+ } else {
+ return new MockNMS(switch(CommandAPIVersionHandler.version.get()) {
+ case MINECRAFT_1_20 -> new NMS_1_20_R1();
+ case MINECRAFT_1_19_4 -> new NMS_1_19_4_R3();
+// case "Minecraft_1_19_2" -> new NMS_1_19_1_R1();
+// case "Minecraft_1_18" -> new NMS_1_18_R1();
+// case "Minecraft_1_17" -> new NMS_1_17();
+// case "Minecraft_1_16_5" -> new NMS_1_16_R3();
+ default -> throw new IllegalArgumentException("Unsupported version value: " + CommandAPIVersionHandler.version.get());
+ });
+ }
+ }
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/ArgumentInspector.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/ArgumentInspector.java
new file mode 100644
index 0000000000..2e25421f25
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/ArgumentInspector.java
@@ -0,0 +1,41 @@
+package dev.jorel.commandapi.test;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+/**
+ * A mutable wrapper of an object, as a list
+ *
+ * @param
+ */
+public class ArgumentInspector {
+
+ public static ArgumentInspector of() {
+ return new ArgumentInspector();
+ }
+
+ private final Deque> value;
+
+ private ArgumentInspector() {
+ value = new ArrayDeque<>();
+ }
+
+ public void set(T obj) {
+ this.value.add(Optional.ofNullable(obj));
+ }
+
+ public T get() {
+ if(this.value.size() == 0) {
+ throw new NoSuchElementException();
+ } else {
+ return this.value.remove().orElse(null);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/Assertions.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/Assertions.java
new file mode 100644
index 0000000000..ed90b7d5f2
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/Assertions.java
@@ -0,0 +1,22 @@
+package dev.jorel.commandapi.test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.bukkit.command.CommandSender;
+
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+
+public class Assertions {
+
+ /**
+ * Checks if the command has invalid syntax (would show up as red in the client)
+ * @param server the server instance
+ * @param sender the command sender
+ * @param command the command that the command sender would run (without the leading {@code /})
+ */
+ public static void assertInvalidSyntax(CommandAPIServerMock server, CommandSender sender, String command) {
+ assertThrows(CommandSyntaxException.class, () -> assertTrue(server.dispatchThrowableCommand(sender,command)));
+ }
+
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/CommandAPIServerMock.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/CommandAPIServerMock.java
new file mode 100644
index 0000000000..4f427189aa
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/CommandAPIServerMock.java
@@ -0,0 +1,89 @@
+package dev.jorel.commandapi.test;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.ParseResults;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestion;
+import com.mojang.brigadier.suggestion.Suggestions;
+
+import be.seeseemelk.mockbukkit.AsyncCatcher;
+import be.seeseemelk.mockbukkit.ServerMock;
+import dev.jorel.commandapi.Brigadier;
+
+public class CommandAPIServerMock extends ServerMock {
+
+ @SuppressWarnings("unchecked")
+ public boolean dispatchThrowableCommand(CommandSender sender, String commandLine) throws CommandSyntaxException{
+ String[] commands = commandLine.split(" ");
+ String commandLabel = commands[0];
+ Command command = getCommandMap().getCommand(commandLabel);
+
+ if(command != null) {
+ return super.dispatchCommand(sender, commandLine);
+ } else {
+ AsyncCatcher.catchOp("command dispatch");
+ @SuppressWarnings("rawtypes")
+ CommandDispatcher dispatcher = Brigadier.getCommandDispatcher();
+ Object css = Brigadier.getBrigadierSourceFromCommandSender(sender);
+ return dispatcher.execute(commandLine, css) != 0;
+ }
+ }
+
+ @Override
+ public boolean dispatchCommand(CommandSender sender, String commandLine) {
+ try {
+ return dispatchThrowableCommand(sender, commandLine);
+ } catch (CommandSyntaxException e1) {
+ return false;
+ }
+ }
+
+ public boolean isValidCommandAPICommand(CommandSender sender, String commandLine) {
+ String[] commands = commandLine.split(" ");
+ String commandLabel = commands[0];
+ Command command = getCommandMap().getCommand(commandLabel);
+
+ if(command != null) {
+ return false;
+ } else {
+ AsyncCatcher.catchOp("command dispatch");
+ @SuppressWarnings("rawtypes")
+ CommandDispatcher dispatcher = Brigadier.getCommandDispatcher();
+ Object css = Brigadier.getBrigadierSourceFromCommandSender(sender);
+ ParseResults results = dispatcher.parse(commandLine, css);
+ return results.getExceptions().size() == 0;
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public List getSuggestions(CommandSender sender, String commandLine) {
+ AsyncCatcher.catchOp("command tabcomplete");
+ CommandDispatcher dispatcher = Brigadier.getCommandDispatcher();
+ Object css = Brigadier.getBrigadierSourceFromCommandSender(sender);
+ ParseResults parseResults = dispatcher.parse(commandLine, css);
+ Suggestions suggestions = null;
+ try {
+ suggestions = (Suggestions) dispatcher.getCompletionSuggestions(parseResults).get();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+
+ List suggestionsAsStrings = new ArrayList<>();
+ for(Suggestion suggestion : suggestions.getList()) {
+ suggestionsAsStrings.add(suggestion.getText());
+ }
+
+ return suggestionsAsStrings;
+ }
+
+ @Override
+ public boolean shouldSendChatPreviews() {
+ return true;
+ }
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/CommandAPITestCommand.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/CommandAPITestCommand.java
new file mode 100644
index 0000000000..923f699fc6
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/CommandAPITestCommand.java
@@ -0,0 +1,64 @@
+package dev.jorel.commandapi.test;
+
+import dev.jorel.commandapi.CommandAPICommand;
+
+public class CommandAPITestCommand extends CommandAPICommand {
+
+ @SuppressWarnings("rawtypes")
+ private final ArgumentInspector listener;
+
+ public CommandAPITestCommand(String commandName) {
+ this(commandName, null);
+ }
+
+ public CommandAPITestCommand(String commandName, @SuppressWarnings("rawtypes") ArgumentInspector listener) {
+ super(commandName);
+ this.listener = listener;
+ }
+
+// // Replace all executors with our 'testing' executor
+// @SuppressWarnings({ "rawtypes", "unchecked" })
+// private void mapExecutor(CustomCommandExecutor customCommandExecutor) {
+// // Do normal executors
+// {
+// ListIterator it = customCommandExecutor.getNormalExecutors().listIterator();
+// while(it.hasNext()) {
+// IExecutorNormal executor = (IExecutorNormal) it.next();
+// IExecutorNormal newExecutor = (sender, args) -> {
+// for(Object arg : args) {
+// listener.set(arg);
+// }
+// executor.executeWith(sender, args);
+// };
+// it.set(newExecutor);
+// }
+// }
+//
+// // And now do resulting executors
+// {
+// ListIterator it = customCommandExecutor.getResultingExecutors().listIterator();
+// while(it.hasNext()) {
+// IExecutorResulting executor = (IExecutorResulting) it.next();
+// IExecutorResulting newExecutor = (sender, args) -> {
+// for(Object arg : args) {
+// listener.set(arg);
+// }
+// return executor.executeWith(sender, args);
+// };
+// it.set(newExecutor);
+// }
+// }
+// }
+//
+// @Override
+// public void register() {
+// if(this.listener != null) {
+// mapExecutor(this.executor);
+// for (CommandAPICommand subcommand : this.getSubcommands()) {
+// mapExecutor(subcommand.getExecutor());
+// }
+// }
+// super.register();
+// }
+
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/Version.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/Version.java
new file mode 100644
index 0000000000..21448b67e6
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/test/Version.java
@@ -0,0 +1,6 @@
+package dev.jorel.commandapi.test;
+
+public enum Version {
+ MINECRAFT_1_20,
+ MINECRAFT_1_19_4;
+}
\ No newline at end of file
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/pom.xml
index 1ace64eff9..a544f46c2a 100644
--- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/pom.xml
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/pom.xml
@@ -68,7 +68,6 @@
org.junit.jupiter
junit-jupiter-engine
5.8.2
- test
org.mockito
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/Main.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/Main.java
index 5b4aaf4461..43c82c3956 100644
--- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/Main.java
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/Main.java
@@ -6,7 +6,6 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
-import de.tr7zw.changeme.nbtapi.NBTContainer;
import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.CommandAPIBukkitConfig;
import dev.jorel.commandapi.CommandAPILogger;
diff --git a/commandapi-platforms/commandapi-bukkit/pom.xml b/commandapi-platforms/commandapi-bukkit/pom.xml
index cc382b4b94..638e59d57f 100644
--- a/commandapi-platforms/commandapi-bukkit/pom.xml
+++ b/commandapi-platforms/commandapi-bukkit/pom.xml
@@ -24,12 +24,17 @@
commandapi-bukkit-vh
commandapi-bukkit-mojang-mapped
+
+ commandapi-bukkit-test
+
commandapi-bukkit-plugin
- commandapi-bukkit-test
commandapi-bukkit-shade
-
commandapi-bukkit-plugin-mojang-mapped
commandapi-bukkit-shade-mojang-mapped
+
+
+ commandapi-bukkit-kotlin
+ commandapi-bukkit-test-toolkit
\ No newline at end of file
diff --git a/examples/commandtestsuite/README.md b/examples/commandtestsuite/README.md
new file mode 100644
index 0000000000..257519b1ee
--- /dev/null
+++ b/examples/commandtestsuite/README.md
@@ -0,0 +1,27 @@
+# Maven-shaded
+
+A simple example of shading the CommandAPI with Maven.
+
+Key points:
+
+- In `Main.java`, we call `CommandAPI.onLoad()` and `CommandAPI.onEnable()` to set up the CommandAPI
+- In `pom.xml`, we use relocation to relocate the CommandAPI to the `io.github.jorelali.commandapi` package. This gives us the following directory structure in our plugin's `.jar` file:
+
+```text
+.
+├── io
+│ └── github
+│ └── jorelali
+│ ├── commandapi
+│ │ ├── arguments
+│ │ │ ├── AdvancementArgument.class
+│ │ │ └── ...
+│ │ ├── CommandAPI.class
+│ │ ├── CommandAPICommand.class
+│ │ └── ...
+│ ├── Main.class
+│ └── MyCommands.class
+├── META-INF
+│ └── MANIFEST.MF
+└── plugin.yml
+```
diff --git a/examples/commandtestsuite/pom.xml b/examples/commandtestsuite/pom.xml
new file mode 100644
index 0000000000..6e34cdfc3e
--- /dev/null
+++ b/examples/commandtestsuite/pom.xml
@@ -0,0 +1,118 @@
+
+ 4.0.0
+ dev.jorel
+ commandtestsuite
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 9.0.4
+
+
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+
+ minecraft-libraries
+ https://libraries.minecraft.net
+
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.8.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.6.1
+
+
+
+ com.mojang
+ brigadier
+ 1.0.17
+ provided
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.19.0
+ provided
+
+
+
+
+ dev.jorel
+ commandapi-bukkit-test-toolkit
+ ${commandapi.version}
+
+
+ dev.jorel
+ commandapi-bukkit-test-impl-1.20
+ ${commandapi.version}
+ test
+
+
+
+
+
+ org.spigotmc
+ spigot
+ 1.20.1-R0.1-SNAPSHOT
+ provided
+
+
+ io.papermc.paper
+ paper-api
+ 1.20-R0.1-SNAPSHOT
+
+
+
+
+ clean package
+
+
+ maven-compiler-plugin
+ 3.8.1
+
+ 16
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.0
+
+ false
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/commandtestsuite/src/main/java/io/github/jorelali/Main.java b/examples/commandtestsuite/src/main/java/io/github/jorelali/Main.java
new file mode 100644
index 0000000000..4f24f74bd7
--- /dev/null
+++ b/examples/commandtestsuite/src/main/java/io/github/jorelali/Main.java
@@ -0,0 +1,49 @@
+package io.github.jorelali;
+
+import java.io.File;
+
+import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.plugin.java.JavaPluginLoader;
+
+import dev.jorel.commandapi.CommandAPI;
+import dev.jorel.commandapi.CommandAPIBukkitConfig;
+
+public class Main extends JavaPlugin {
+
+ // Additional constructors required for MockBukkit
+ public Main() {
+ super();
+ }
+
+ public Main(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) {
+ super(loader, description, dataFolder, file);
+ }
+
+ @Override
+ public void onLoad() {
+ // Load the CommandAPI. We enable verbose logging and allow the CommandAPI
+ // to generate a file command_registration.json for debugging purposes
+ CommandAPI.onLoad(
+ new CommandAPIBukkitConfig(this)
+ .verboseOutput(true)
+ .dispatcherFile(new File(getDataFolder(), "command_registration.json")));
+ }
+
+ @Override
+ public void onEnable() {
+ // Enable the CommandAPI
+ CommandAPI.onEnable();
+
+ // Register my commands as normal
+ MyCommands myCommands = new MyCommands(this);
+ myCommands.registerAllCommands();
+ }
+
+ @Override
+ public void onDisable() {
+ // Disable the CommandAPI
+ CommandAPI.onDisable();
+ }
+
+}
diff --git a/examples/commandtestsuite/src/main/java/io/github/jorelali/MyCommands.java b/examples/commandtestsuite/src/main/java/io/github/jorelali/MyCommands.java
new file mode 100644
index 0000000000..80230de1ea
--- /dev/null
+++ b/examples/commandtestsuite/src/main/java/io/github/jorelali/MyCommands.java
@@ -0,0 +1,51 @@
+package io.github.jorelali;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+
+import dev.jorel.commandapi.CommandAPICommand;
+import dev.jorel.commandapi.arguments.LocationArgument;
+import dev.jorel.commandapi.arguments.LocationType;
+import dev.jorel.commandapi.arguments.PlayerArgument;
+import dev.jorel.commandapi.arguments.PotionEffectArgument;
+
+public class MyCommands {
+
+ // Plugin reference in case we need to access anything about the plugin,
+ // such as its config.yml or something
+ private Plugin plugin;
+
+ public MyCommands(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public void registerAllCommands() {
+ // /break
+ // Breaks a block at . Can only be executed by a player
+ new CommandAPICommand("break")
+ // We want to target blocks in particular, so use BLOCK_POSITION
+ .withArguments(new LocationArgument("block", LocationType.BLOCK_POSITION))
+ .executesPlayer((player, args) -> {
+ ((Location) args.get("block")).getBlock().breakNaturally();
+ })
+ .register();
+
+ // /myeffect
+ // Applies a potion effect to a player. Basically a simple version of
+ // the /effect command. The potion effect with be a level 1 potion
+ // effect and the duration will be 5 minutes (300 seconds x 20 ticks)
+ new CommandAPICommand("myeffect")
+ .withArguments(new PlayerArgument("target"))
+ .withArguments(new PotionEffectArgument("potion"))
+ .executes((sender, args) -> {
+ Player target = (Player) args.get("target");
+ PotionEffectType potionEffectType = (PotionEffectType) args.get("potion");
+ target.addPotionEffect(new PotionEffect(potionEffectType, 300 * 20, 1));
+ })
+ .register();
+ }
+
+}
diff --git a/examples/commandtestsuite/src/main/resources/plugin.yml b/examples/commandtestsuite/src/main/resources/plugin.yml
new file mode 100644
index 0000000000..b57b948be8
--- /dev/null
+++ b/examples/commandtestsuite/src/main/resources/plugin.yml
@@ -0,0 +1,6 @@
+name: Example Plugin
+main: io.github.jorelali.Main
+version: 0.0.1
+author: Skepter
+website: https://www.jorel.dev/CommandAPI/
+api-version: 1.13
\ No newline at end of file
diff --git a/examples/commandtestsuite/src/test/java/io/github/jorelali/MyTests.java b/examples/commandtestsuite/src/test/java/io/github/jorelali/MyTests.java
new file mode 100644
index 0000000000..f8a4d4d5dd
--- /dev/null
+++ b/examples/commandtestsuite/src/test/java/io/github/jorelali/MyTests.java
@@ -0,0 +1,91 @@
+package io.github.jorelali;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+
+import be.seeseemelk.mockbukkit.MockBukkit;
+import be.seeseemelk.mockbukkit.entity.PlayerMock;
+import dev.jorel.commandapi.CommandAPIVersionHandler;
+import dev.jorel.commandapi.test.CommandAPIServerMock;
+import dev.jorel.commandapi.test.MockPlatform;
+import dev.jorel.commandapi.test.Version;
+
+class MyTests {
+
+ ////////////////
+ // Test setup //
+ ////////////////
+
+ private CommandAPIServerMock server; // The server
+ private Main plugin; // Our plugin
+
+ // Set up a test for Minecraft 1.20
+
+ @BeforeEach
+ public void setUp() {
+ CommandAPIVersionHandler.setVersion(Version.MINECRAFT_1_20);
+ server = MockBukkit.mock(new CommandAPIServerMock());
+ plugin = MockBukkit.load(Main.class);
+ }
+
+ // Teardown our tests.
+
+ @AfterEach
+ public void tearDown() {
+ if (server != null) {
+ Bukkit.getScheduler().cancelTasks(plugin);
+ if (plugin != null) {
+ plugin.onDisable();
+ }
+ MockBukkit.unmock();
+ }
+ server = null;
+ plugin = null;
+ MockPlatform.unload();
+ }
+
+ ///////////
+ // Tests //
+ ///////////
+
+ @Test
+ public void executionTest() {
+ PlayerMock player = server.addPlayer("myname");
+
+ server.dispatchCommand(player, "myeffect myname speed");
+
+ assertEquals(1, player.getActivePotionEffects().size());
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Utility methods, probably stuff that the CommandAPI Test Toolkit will //
+ // include, but I'm including them here for now until I figure out what //
+ // features we want //
+ ///////////////////////////////////////////////////////////////////////////
+
+ private String getDispatcherString() {
+ try {
+ return Files.readString(new File("command_registration.json").toPath());
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ public void assertInvalidSyntax(CommandSender sender, String command) {
+ assertThrows(CommandSyntaxException.class, () -> assertTrue(server.dispatchThrowableCommand(sender, command)));
+ }
+
+}
diff --git a/examples/commandtestsuite/src/test/resources/plugin.yml b/examples/commandtestsuite/src/test/resources/plugin.yml
new file mode 100644
index 0000000000..b57b948be8
--- /dev/null
+++ b/examples/commandtestsuite/src/test/resources/plugin.yml
@@ -0,0 +1,6 @@
+name: Example Plugin
+main: io.github.jorelali.Main
+version: 0.0.1
+author: Skepter
+website: https://www.jorel.dev/CommandAPI/
+api-version: 1.13
\ No newline at end of file