From b77a38b394ba6fb98d2e8f292a98fbf5aa2bee89 Mon Sep 17 00:00:00 2001 From: marcos Date: Thu, 6 Aug 2026 22:27:42 +0000 Subject: [PATCH] Make zoacao match rule editable in-game: mode + pattern + message list Zoacao now matches a configurable pattern under a configurable mode (igual/contem/comeca/termina/regex) instead of only a bare 'f'. The mode, the trigger pattern, and the gag message list are all editable live via /canalhandia zoacao ; every change writes through to config.yml immediately. config.yml gains zoacao.correspondencia + zoacao.padrao (default igual + 'f'). Zoacao.replace/ matches gained a Mode + pattern signature; ZoacaoTest covers all five modes (incl. invalid-regex guard) and the byKey parser. 136 tests green. Co-Authored-By: Claude --- README.md | 8 +- .../marcospaulo/canalhandia/Canalhandia.java | 13 +- .../canalhandia/CanalhandiaCommand.java | 108 +++++++++++++- .../dev/marcospaulo/canalhandia/Settings.java | 31 +++- .../dev/marcospaulo/canalhandia/Zoacao.java | 87 +++++++++++- src/main/resources/config.yml | 15 +- .../marcospaulo/canalhandia/ZoacaoTest.java | 134 ++++++++++++++---- 7 files changed, 351 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 6d127b6..f981640 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ All player-facing text is Portuguese (pt-BR). | `ranking` | `/ranking mineracao` and friends. Covers **offline players too**. | | `marcos` | Announces round milestones — 100 km walked, 24 hours played — the first time someone crosses one. | | `mortes` | Replaces each death message with a comic pt-BR line (cause-based flavor + a death counter) and sends the death coordinates **privately** to the dead player on respawn (the death screen swallows chat sent during the event), so they can run back to their dropped items. Respects `keepInventory`. No storage, no command. | -| `zoacao` | A bare `f`/`F` in chat (trimmed, nothing else) is swapped for a random line from `zoacao.mensagens` — a chat gag. The player's name still prefixes it. Pure chat swap; the `luto` tribute is unaffected (paying respects still needs the `[F]` button or `/f`). Affects Bedrock chat too (it's a chat event, not a click). | +| `zoacao` | A chat message matching the trigger (a pattern + a match mode) is swapped for a random line from `zoacao.mensagens` — a chat gag. The player's name still prefixes it. Default: a bare `f`/`F` (trimmed) → a random gag line. Match modes: `igual` (equals), `contem` (contains), `comeca` (starts with), `termina` (ends with), `regex`. The mode, the pattern, and the message list are all editable in-game with `/canalhandia zoacao ...`. Pure chat swap; the `luto` tribute is unaffected (paying respects still needs the `[F]` button or `/f`). Affects Bedrock chat too (it's a chat event, not a click). | | `ia` | `/ia ` asks an OpenAI-compatible model in chat. Optional wiki grounding, per-player memory, operator corrections. | Toggle any of them: `/canalhandia modulo ` @@ -164,6 +164,12 @@ Admin (`canalhandia.admin`): /canalhandia modulo liga/desliga um módulo /canalhandia marcos força uma verificação de marcos /canalhandia limpar [cooldown|historico|tudo] +/canalhandia zoacao listar mostra a regra e as frases da zoação +/canalhandia zoacao modo igual | contem | comeca | termina | regex +/canalhandia zoacao padrao texto/regex que dispara a zoação +/canalhandia zoacao adicionar adiciona uma frase de zoação +/canalhandia zoacao remover remove uma frase de zoação +/canalhandia zoacao limpar volta para as frases padrão /canalhandia reload /curiosidade modo /curiosidade intervalo intervalo do modo temporizado diff --git a/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java b/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java index dbba016..7b0a6a2 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java @@ -480,17 +480,20 @@ public final class Canalhandia extends JavaPlugin implements Listener { } /** - * Chat gag: a bare "f" (or "F", trimmed, nothing else) gets replaced with a - * random line from {@code zoacao.mensagens}. Pure chat swap — the player's - * name still prefixes it as normal. Independent of {@code luto}: paying - * respects still needs the {@code [F]} button (Java) or {@code /f} command. + * Chat gag: a message matching the {@code zoacao} trigger (pattern + match + * mode, default a bare "f") gets replaced with a random line from + * {@code zoacao.mensagens}. Pure chat swap — the player's name still + * prefixes it as normal. Independent of {@code luto}: paying respects still + * needs the {@code [F]} button (Java) or {@code /f} command. Editable + * in-game via {@code /canalhandia zoacao ...}. */ @EventHandler public void onChatF(AsyncPlayerChatEvent event) { if (!settings.moduleEnabled(Module.ZOACAO)) { return; } - String gag = Zoacao.replace(event.getMessage(), settings.zoacaoMessages(), random); + String gag = Zoacao.replace(event.getMessage(), settings.zoacaoMode(), + settings.zoacaoPattern(), settings.zoacaoMessages(), random); if (gag != null) { event.setMessage(gag); } diff --git a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java index 061d36e..a16b82d 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java +++ b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java @@ -89,6 +89,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { } } case "limpar" -> clear(sender, rest); + case "zoacao" -> zoacaoEdit(sender, rest); case "reload" -> { if (admin(sender)) { plugin.reloadConfig(); @@ -619,6 +620,97 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { } } + /** + * In-game editing of the {@code zoacao} chat gag: the match mode, the + * trigger pattern, and the replacement message list. Admin-only; every + * change writes through to config.yml immediately. + */ + private void zoacaoEdit(CommandSender sender, String[] args) { + if (!admin(sender)) { + return; + } + Settings settings = plugin.settings(); + String sub = args.length > 0 ? args[0].toLowerCase(Locale.ROOT) : "listar"; + switch (sub) { + case "listar", "status" -> { + Msg.header(sender, "Zoação (gag de chat)"); + Msg.line(sender, "correspondência", settings.zoacaoMode().key() + + " (igual | contem | comeca | termina | regex)"); + Msg.line(sender, "padrão", settings.zoacaoPattern()); + List messages = settings.zoacaoMessages(); + Msg.line(sender, "mensagens", "(" + messages.size() + ")"); + for (int i = 0; i < messages.size(); i++) { + sender.sendMessage(Component.text(" " + (i + 1) + ". ", NamedTextColor.DARK_AQUA) + .append(Component.text(messages.get(i), NamedTextColor.WHITE))); + } + } + case "modo" -> { + if (args.length < 2) { + Msg.error(sender, "Uso: /canalhandia zoacao modo "); + return; + } + Zoacao.Mode mode = Zoacao.Mode.byKey(args[1]); + if (mode == null) { + Msg.error(sender, "Modo inválido. Use: igual, contem, comeca, termina, regex."); + return; + } + settings.zoacaoMode(mode); + Msg.ok(sender, "Correspondência da zoação: " + mode.key() + "."); + } + case "padrao" -> { + if (args.length < 2) { + Msg.error(sender, "Uso: /canalhandia zoacao padrao (atual: " + settings.zoacaoPattern() + ")"); + return; + } + String pattern = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + settings.zoacaoPattern(pattern); + Msg.ok(sender, "Padrão da zoação: " + pattern + "."); + } + case "adicionar", "add" -> { + if (args.length < 2) { + Msg.error(sender, "Uso: /canalhandia zoacao adicionar "); + return; + } + String line = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + List messages = new ArrayList<>(settings.zoacaoMessages()); + messages.add(line); + settings.zoacaoMessages(messages); + Msg.ok(sender, "Adicionado: " + line + " (agora " + messages.size() + " mensagens)."); + } + case "remover", "remove" -> { + List messages = new ArrayList<>(settings.zoacaoMessages()); + if (args.length < 2) { + Msg.error(sender, "Uso: /canalhandia zoacao remover "); + return; + } + String target = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + int index = parse(target, Integer.MIN_VALUE); + boolean removed; + if (index != Integer.MIN_VALUE) { + int idx = index - 1; + removed = idx >= 0 && idx < messages.size(); + if (removed) { + messages.remove(idx); + } + } else { + removed = messages.removeIf(m -> m.equalsIgnoreCase(target)); + } + if (!removed) { + Msg.error(sender, "Não encontrei '" + target + "' na lista."); + return; + } + settings.zoacaoMessages(messages); + Msg.ok(sender, "Removido. Restam " + messages.size() + " mensagens."); + } + case "limpar" -> { + // Clear the configured list so the built-in defaults come back. + settings.zoacaoMessages(List.of()); + Msg.ok(sender, "Lista limpa — voltou para as mensagens padrão."); + } + default -> Msg.error(sender, "Uso: /canalhandia zoacao "); + } + } + private void clear(CommandSender sender, String[] args) { if (!admin(sender)) { return; @@ -666,6 +758,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { } } Msg.line(sender, "categorias ativas", enabled.isEmpty() ? "nenhuma" : String.join(", ", enabled)); + Msg.line(sender, "zoação", settings.zoacaoMode().key() + " '" + settings.zoacaoPattern() + + "' (" + settings.zoacaoMessages().size() + " mensagens)"); Msg.line(sender, "ia", plugin.ai().configured() ? settings.aiModel() + " · perfil " + settings.aiProfile().name().toLowerCase(Locale.ROOT) + " · " + plugin.ai().askedToday() + "/" + settings.aiDailyLimit() + " hoje" @@ -724,6 +818,12 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { commands.put("/curiosidade reacao add ", "cria ou muda uma reação"); commands.put("/curiosidade reacao remover ", "remove uma reação"); commands.put("/curiosidade categoria ", "liga/desliga uma categoria"); + commands.put("/canalhandia zoacao listar", "mostra a regra e as frases da zoação"); + commands.put("/canalhandia zoacao modo ", "igual | contem | comeca | termina | regex"); + commands.put("/canalhandia zoacao padrao ", "texto/regex que dispara a zoação"); + commands.put("/canalhandia zoacao adicionar ", "adiciona uma frase de zoação"); + commands.put("/canalhandia zoacao remover ", "remove uma frase de zoação"); + commands.put("/canalhandia zoacao limpar", "volta para as frases padrão"); } commands.forEach((cmd, description) -> sender.sendMessage( Component.text(" " + cmd, NamedTextColor.AQUA) @@ -893,7 +993,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { if (args.length == 1) { List options = new ArrayList<>(List.of("status", "modulos", "plataformas")); if (sender.hasPermission(ADMIN)) { - options.addAll(List.of("modulo", "marcos", "limpar", "reload")); + options.addAll(List.of("modulo", "marcos", "limpar", "zoacao", "reload")); } return filter(options, args[0]); } @@ -907,6 +1007,12 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { if (args.length == 2 && args[0].equalsIgnoreCase("limpar")) { return filter(List.of("cooldown", "historico", "tudo"), args[1]); } + if (args.length == 2 && args[0].equalsIgnoreCase("zoacao")) { + return filter(List.of("listar", "modo", "padrao", "adicionar", "remover", "limpar"), args[1]); + } + if (args.length == 3 && args[0].equalsIgnoreCase("zoacao") && args[1].equalsIgnoreCase("modo")) { + return filter(List.of("igual", "contem", "comeca", "termina", "regex"), args[2]); + } if (args.length == 3 && args[0].equalsIgnoreCase("modulo")) { return filter(List.of("on", "off"), args[2]); } diff --git a/src/main/java/dev/marcospaulo/canalhandia/Settings.java b/src/main/java/dev/marcospaulo/canalhandia/Settings.java index 9835401..c2614a3 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Settings.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Settings.java @@ -347,9 +347,9 @@ final class Settings { // --- zoacao (f-gag) ----------------------------------------------------- /** - * Lines a bare "f" in chat gets replaced with, picked at random. Defaults - * to a small built-in list if unset/empty so the feature works out of the - * box; operators edit {@code zoacao.mensagens} in config.yml to customise. + * Lines a matching chat message gets replaced with, picked at random. + * Defaults to a small built-in list if unset/empty so the feature works out + * of the box; operators edit {@code zoacao.mensagens} in-game to customise. */ List zoacaoMessages() { List messages = plugin.getConfig().getStringList("zoacao.mensagens"); @@ -365,6 +365,31 @@ final class Settings { return messages; } + /** Writes the full message list through to config immediately. */ + void zoacaoMessages(List messages) { + plugin.getConfig().set("zoacao.mensagens", messages); + plugin.saveConfig(); + } + + /** How a chat message is tested against the trigger pattern. */ + Zoacao.Mode zoacaoMode() { + return Zoacao.Mode.byKeyOrDefault(plugin.getConfig().getString("zoacao.correspondencia", "igual"), + Zoacao.Mode.IGUAL); + } + + void zoacaoMode(Zoacao.Mode mode) { + set("zoacao.correspondencia", mode.key()); + } + + /** The trigger text (or regex for the {@code regex} mode). */ + String zoacaoPattern() { + return plugin.getConfig().getString("zoacao.padrao", "f"); + } + + void zoacaoPattern(String pattern) { + set("zoacao.padrao", pattern); + } + // --- content ------------------------------------------------------------ boolean categoryEnabled(Category category) { diff --git a/src/main/java/dev/marcospaulo/canalhandia/Zoacao.java b/src/main/java/dev/marcospaulo/canalhandia/Zoacao.java index 75ac72d..ffadcb6 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Zoacao.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Zoacao.java @@ -1,37 +1,110 @@ package dev.marcospaulo.canalhandia; import java.util.List; +import java.util.Locale; import java.util.Random; +import java.util.regex.Pattern; /** - * Pure logic for the {@code zoacao} chat gag: when a player sends a bare - * {@code "f"} (or {@code "F"}, trimmed, nothing else) their message is swapped - * for a random line from a configurable list. Extracted so the trigger rule is + * Pure logic for the {@code zoacao} chat gag: when a player's chat message + * matches a configurable trigger (a pattern + a match mode), it is swapped + * for a random line from a configurable list. Extracted so the match rule is * unit-testable without a server. * + *

Match modes: + *

    + *
  • {@link Mode#IGUAL} — message equals the pattern (ignoring case, trimmed)
  • + *
  • {@link Mode#CONTEM} — message contains the pattern (case-insensitive)
  • + *
  • {@link Mode#COMECA} — message starts with the pattern
  • + *
  • {@link Mode#TERMINA} — message ends with the pattern
  • + *
  • {@link Mode#REGEX} — pattern is a case-insensitive regex, matched anywhere
  • + *
+ * *

This is a standalone chat gag. It does not interact with the {@code luto} * tribute — paying respects still happens via the {@code [F]} button (Java) or * the {@code /f} command (Bedrock), neither of which is a chat message. */ final class Zoacao { + /** How a chat message is tested against the trigger pattern. */ + enum Mode { + IGUAL("igual"), + CONTEM("contem"), + COMECA("comeca"), + TERMINA("termina"), + REGEX("regex"); + + private final String key; + + Mode(String key) { + this.key = key; + } + + String key() { + return key; + } + + static Mode byKey(String key) { + for (Mode mode : values()) { + if (mode.key.equalsIgnoreCase(key)) { + return mode; + } + } + return null; + } + + static Mode byKeyOrDefault(String key, Mode fallback) { + Mode mode = byKey(key); + return mode == null ? fallback : mode; + } + } + private Zoacao() { } /** * @param message the chat message as sent by the player + * @param mode how to test the message against the pattern + * @param pattern the trigger text (or regex for {@link Mode#REGEX}) * @param gags the configured replacement lines; if null/empty, no gag * @param random shared random used to pick a line - * @return the replacement line if the message is a bare "f", otherwise null + * @return the replacement line if the message matches, otherwise null * (meaning "leave the message alone") */ - static String replace(String message, List gags, Random random) { - if (message == null || gags == null || gags.isEmpty()) { + static String replace(String message, Mode mode, String pattern, List gags, Random random) { + if (message == null || gags == null || gags.isEmpty() || pattern == null || pattern.isBlank()) { return null; } - if (!message.trim().equalsIgnoreCase("f")) { + if (!matches(message, mode, pattern)) { return null; } return gags.get(random.nextInt(gags.size())); } + + /** Pure test of one message against one pattern under one mode. */ + static boolean matches(String message, Mode mode, String pattern) { + if (message == null || mode == null || pattern == null) { + return false; + } + String trimmed = message.trim(); + String needle = pattern.toLowerCase(Locale.ROOT); + switch (mode) { + case IGUAL: + return trimmed.equalsIgnoreCase(pattern); + case CONTEM: + return trimmed.toLowerCase(Locale.ROOT).contains(needle); + case COMECA: + return trimmed.toLowerCase(Locale.ROOT).startsWith(needle); + case TERMINA: + return trimmed.toLowerCase(Locale.ROOT).endsWith(needle); + case REGEX: + try { + return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(trimmed).find(); + } catch (java.util.regex.PatternSyntaxException e) { + return false; + } + default: + return false; + } + } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 61ac83b..365aefb 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -71,9 +71,20 @@ janela-reacao-segundos: 90 luto: cabeca: true -# Frases que substituem um "f" sozinho no chat (módulo zoacao). Uma é sorteada -# por mensagem. Edite à vontade — a graça é ser inesperado. +# Zoa de quem manda uma mensagem que bate com o padrão, trocando por uma frase +# engraçada. Tudo editável em jogo com /canalhandia zoacao ... zoacao: + # Como comparar a mensagem do chat com o padrão: + # igual - mensagem inteira igual ao padrão (ignora maiúsculas e espaços) + # contem - mensagem contém o padrão em qualquer lugar + # comeca - mensagem começa com o padrão + # termina - mensagem termina com o padrão + # regex - padrão é uma expressão regular (maiúsculas ignoradas) + correspondencia: igual + # Texto (ou regex) que dispara a zoação. Padrão: só um "f" sozinho. + padrao: "f" + # Frases que substituem a mensagem. Uma é sorteada por vez. Edite à vontade — + # a graça é ser inesperado. mensagens: - "Sou gay" - "Gosto de anime" diff --git a/src/test/java/dev/marcospaulo/canalhandia/ZoacaoTest.java b/src/test/java/dev/marcospaulo/canalhandia/ZoacaoTest.java index 3229b0a..4efde9c 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/ZoacaoTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/ZoacaoTest.java @@ -1,6 +1,7 @@ package dev.marcospaulo.canalhandia; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -13,48 +14,129 @@ class ZoacaoTest { private static final List GAGS = List.of( "Sou gay", "Gosto de anime", "Jogo no celular"); + // --- Mode.byKey --------------------------------------------------------- + @Test - void bareLowercaseFIsReplacedWithAGagFromTheList() { - String gag = Zoacao.replace("f", GAGS, new Random(0L)); + void modeByKeyParsesEachMode() { + assertEquals(Zoacao.Mode.IGUAL, Zoacao.Mode.byKey("igual")); + assertEquals(Zoacao.Mode.CONTEM, Zoacao.Mode.byKey("contem")); + assertEquals(Zoacao.Mode.COMECA, Zoacao.Mode.byKey("comeca")); + assertEquals(Zoacao.Mode.TERMINA, Zoacao.Mode.byKey("termina")); + assertEquals(Zoacao.Mode.REGEX, Zoacao.Mode.byKey("regex")); + } + + @Test + void modeByKeyIsCaseInsensitive() { + assertEquals(Zoacao.Mode.CONTEM, Zoacao.Mode.byKey("CONTEM")); + } + + @Test + void modeByKeyReturnsNullForUnknown() { + assertNull(Zoacao.Mode.byKey("exato")); + } + + @Test + void modeByKeyOrDefaultFallsBack() { + assertEquals(Zoacao.Mode.IGUAL, Zoacao.Mode.byKeyOrDefault("xx", Zoacao.Mode.IGUAL)); + assertEquals(Zoacao.Mode.CONTEM, Zoacao.Mode.byKeyOrDefault("contem", Zoacao.Mode.IGUAL)); + } + + // --- matches / replace: IGUAL ------------------------------------------ + + @Test + void igualBareLowercaseFMatches() { + assertTrue(Zoacao.matches("f", Zoacao.Mode.IGUAL, "f")); + } + + @Test + void igualBareUppercaseFMatches() { + assertTrue(Zoacao.matches("F", Zoacao.Mode.IGUAL, "f")); + } + + @Test + void igualSurroundingWhitespaceStillMatches() { + assertTrue(Zoacao.matches(" f ", Zoacao.Mode.IGUAL, "f")); + assertTrue(Zoacao.matches("\tF\n", Zoacao.Mode.IGUAL, "f")); + } + + @Test + void igualFWithAnythingElseDoesNotMatch() { + assertFalse(Zoacao.matches("f lol", Zoacao.Mode.IGUAL, "f")); + assertFalse(Zoacao.matches("ff", Zoacao.Mode.IGUAL, "f")); + assertFalse(Zoacao.matches("pra você f", Zoacao.Mode.IGUAL, "f")); + } + + // --- matches: CONTEM / COMECA / TERMINA --------------------------------- + + @Test + void contemMatchesAnywhere() { + assertTrue(Zoacao.matches("aaaffffaaa", Zoacao.Mode.CONTEM, "fff")); + assertTrue(Zoacao.matches("morte do f cara", Zoacao.Mode.CONTEM, "f")); + assertFalse(Zoacao.matches("oi", Zoacao.Mode.CONTEM, "f")); + } + + @Test + void comecaMatchesAtStart() { + assertTrue(Zoacao.matches("f para o morto", Zoacao.Mode.COMECA, "f")); + assertTrue(Zoacao.matches("FFFreak", Zoacao.Mode.COMECA, "f")); + assertFalse(Zoacao.matches("oi f", Zoacao.Mode.COMECA, "f")); + } + + @Test + void terminaMatchesAtEnd() { + assertTrue(Zoacao.matches("press f", Zoacao.Mode.TERMINA, "f")); + assertTrue(Zoacao.matches("mais F", Zoacao.Mode.TERMINA, "f")); + assertFalse(Zoacao.matches("f oi", Zoacao.Mode.TERMINA, "f")); + } + + // --- matches: REGEX ----------------------------------------------------- + + @Test + void regexMatchesAnywhereCaseInsensitive() { + assertTrue(Zoacao.matches("drop f na fogueira", Zoacao.Mode.REGEX, "\\bf\\b")); + assertTrue(Zoacao.matches("FFFFFFFF", Zoacao.Mode.REGEX, "f+")); + assertFalse(Zoacao.matches("floresta", Zoacao.Mode.REGEX, "^f$")); + } + + @Test + void regexInvalidPatternDoesNotMatch() { + assertFalse(Zoacao.matches("f", Zoacao.Mode.REGEX, "([")); + } + + // --- replace ----------------------------------------------------------- + + @Test + void replaceReturnsAGagWhenMatch() { + String gag = Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", GAGS, new Random(0L)); assertTrue(GAGS.contains(gag), "expected a gag from the list, got " + gag); } @Test - void bareUppercaseFIsReplaced() { - // A single-element list makes the random pick deterministic. - String gag = Zoacao.replace("F", List.of("alvo"), new Random(0L)); - assertEquals("alvo", gag); + void replaceSingleElementListIsDeterministic() { + assertEquals("alvo", + Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", List.of("alvo"), new Random(0L))); } @Test - void surroundingWhitespaceStillCountsAsBareF() { - assertEquals("alvo", Zoacao.replace(" f ", List.of("alvo"), new Random(0L))); - assertEquals("alvo", Zoacao.replace("\tF\n", List.of("alvo"), new Random(0L))); + void replaceReturnsNullWhenNoMatch() { + assertNull(Zoacao.replace("oi", Zoacao.Mode.IGUAL, "f", GAGS, new Random(0L))); } @Test - void fWithAnythingElseIsLeftAlone() { - assertNull(Zoacao.replace("f lol", GAGS, new Random(0L))); - assertNull(Zoacao.replace("ff", GAGS, new Random(0L))); - assertNull(Zoacao.replace("f.", GAGS, new Random(0L))); - assertNull(Zoacao.replace("pra você f", GAGS, new Random(0L))); + void replaceReturnsNullForEmptyOrNullGags() { + assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", List.of(), new Random(0L))); + assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", null, new Random(0L))); } @Test - void nonFMessagesAreLeftAlone() { - assertNull(Zoacao.replace("oi", GAGS, new Random(0L))); - assertNull(Zoacao.replace("", GAGS, new Random(0L))); - assertNull(Zoacao.replace("F para o morto", GAGS, new Random(0L))); + void replaceReturnsNullForBlankOrNullPattern() { + assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, "", GAGS, new Random(0L))); + assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, " ", GAGS, new Random(0L))); + assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, null, GAGS, new Random(0L))); } @Test - void emptyOrNullGagsLeaveMessageAlone() { - assertNull(Zoacao.replace("f", List.of(), new Random(0L))); - assertNull(Zoacao.replace("f", null, new Random(0L))); - } - - @Test - void nullMessageLeftAlone() { - assertNull(Zoacao.replace(null, GAGS, new Random(0L))); + void replaceReturnsNullForNullMessage() { + assertNull(Zoacao.replace(null, Zoacao.Mode.IGUAL, "f", GAGS, new Random(0L))); } } \ No newline at end of file