diff --git a/src/main/java/world/bentobox/level/util/ConversationUtils.java b/src/main/java/world/bentobox/level/util/ConversationUtils.java index 968f19e..6a13dc1 100644 --- a/src/main/java/world/bentobox/level/util/ConversationUtils.java +++ b/src/main/java/world/bentobox/level/util/ConversationUtils.java @@ -43,6 +43,16 @@ public static void createStringInput(Consumer consumer, @NonNull String question, @Nullable String successMessage) { + if (user.getPlayer().isConversing()) + { + // Bukkit queues conversations per player, so starting another one here would + // stack prompts that later replay as cancel/prompt spam (#451). Repeat the + // question for the pending conversation instead. + user.closeInventory(); + user.getPlayer().sendRawMessage(user.getTranslation("level.conversations.prefix") + question); + return; + } + // Text input message. StringPrompt stringPrompt = new StringPrompt() { diff --git a/src/test/java/world/bentobox/level/util/ConversationUtilsTest.java b/src/test/java/world/bentobox/level/util/ConversationUtilsTest.java new file mode 100644 index 0000000..ac6ff4f --- /dev/null +++ b/src/test/java/world/bentobox/level/util/ConversationUtilsTest.java @@ -0,0 +1,62 @@ +package world.bentobox.level.util; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.function.Consumer; + +import org.bukkit.conversations.Conversation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.level.CommonTestSetup; + +/** + * Tests for {@link ConversationUtils}. + */ +class ConversationUtilsTest extends CommonTestSetup { + + private User user; + + @Override + @BeforeEach + protected void setUp() throws Exception { + super.setUp(); + // The conversation timeout canceller starts a scheduler timer on build. + when(plugin.getServer()).thenReturn(server); + user = User.getInstance(p); + } + + /** + * A player with no active conversation should get a new one. + */ + @Test + void testCreateStringInputStartsConversation() { + when(p.isConversing()).thenReturn(false); + + Consumer consumer = value -> {}; + ConversationUtils.createStringInput(consumer, user, "question?", "done"); + + verify(p).beginConversation(any(Conversation.class)); + } + + /** + * Clicking the search button while a conversation is already pending must not + * queue a second conversation (#451) — it should just repeat the question. + */ + @Test + void testCreateStringInputDoesNotQueueSecondConversation() { + when(p.isConversing()).thenReturn(true); + + Consumer consumer = value -> {}; + ConversationUtils.createStringInput(consumer, user, "question?", "done"); + + verify(p, never()).beginConversation(any(Conversation.class)); + verify(p).closeInventory(); + verify(p).sendRawMessage(anyString()); + } +}