diff --git a/Common/build.gradle.kts b/Common/build.gradle.kts index ccc36266..72a078a4 100644 --- a/Common/build.gradle.kts +++ b/Common/build.gradle.kts @@ -3,12 +3,4 @@ version = "1.1.0.7" dependencies { api(projects.api) - - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") -} - -tasks.test { - useJUnitPlatform() } \ No newline at end of file diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java index ea5ed05b..4471ac00 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java @@ -19,9 +19,7 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; @@ -30,41 +28,11 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class ComponentMeta implements PaperMetaUpdater { - - private static final Pattern LEGACY_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}"); - private static final Pattern HEX_SHORT_PATTERN = Pattern.compile("(? COLORS_MAPPINGS = Map.ofEntries( - Map.entry("0", "black"), - Map.entry("1", "dark_blue"), - Map.entry("2", "dark_green"), - Map.entry("3", "dark_aqua"), - Map.entry("4", "dark_red"), - Map.entry("5", "dark_purple"), - Map.entry("6", "gold"), - Map.entry("7", "gray"), - Map.entry("8", "dark_gray"), - Map.entry("9", "blue"), - Map.entry("a", "green"), - Map.entry("b", "aqua"), - Map.entry("c", "red"), - Map.entry("d", "light_purple"), - Map.entry("e", "yellow"), - Map.entry("f", "white"), - Map.entry("k", "obfuscated"), - Map.entry("l", "bold"), - Map.entry("m", "strikethrough"), - Map.entry("n", "underlined"), - Map.entry("o", "italic"), - Map.entry("r", "reset") - ); private final SimpleCache cache = new SimpleCache<>(); private final Method getLoreMethod; private final Method setLoreMethod; @@ -92,9 +60,6 @@ private TextDecoration.State getState(String text) { return text.contains("&o") || text.contains("") || text.contains("") || text.contains("") ? TextDecoration.State.TRUE : TextDecoration.State.FALSE; } - private void test(ItemStack itemStack){ - } - @Override public @NonNull Component getComponent(String text) { return this.cache.get(text, ()->this.MINI_MESSAGE.deserialize(colorMiniMessage(text))); @@ -202,22 +167,7 @@ private Inventory createInventoryInternal(String inventoryName, InventoryHolder } - private String colorMiniMessage(String message) { - String newMessage = message; - - // §x§r§g§b§2§f§3 → <#rgb2f3> - newMessage = convertLegacyHex(newMessage); - // &#a1b2c3 → <#a1b2c3> - newMessage = convertShorLegacyHex(newMessage); - // #a1b2c3 → <#a1b2c3> - newMessage = newMessage.replaceAll("(?"); - - // &a → , §c → , etc. - newMessage = replaceLegacyColors(newMessage); - - return newMessage; - } @Override public void sendMessage(@NonNull CommandSender sender, @NonNull String message) { @@ -259,42 +209,4 @@ public void openBook(@NonNull Player player, @NonNull String title, @NonNull Str public String getLegacyMessage(String message) { return LegacyComponentSerializer.legacySection().serialize(this.getComponent(message)); } - - private @NotNull String convertLegacyHex(String message) { - Matcher matcher = LEGACY_HEX_PATTERN.matcher(message); - StringBuilder sb = new StringBuilder(); - - while (matcher.find()) { - String hex = matcher.group().replaceAll("§x|§", ""); - matcher.appendReplacement(sb, "<#" + hex + ">"); - } - - matcher.appendTail(sb); - return sb.toString(); - } - - private @NotNull String convertShorLegacyHex(String message) { - Matcher matcher = HEX_SHORT_PATTERN.matcher(message); - StringBuilder sb = new StringBuilder(); - - while (matcher.find()) { - matcher.appendReplacement(sb, "<#" + matcher.group(1) + ">"); - } - - matcher.appendTail(sb); - return sb.toString(); - } - - private String replaceLegacyColors(String message) { - for (var entry : this.COLORS_MAPPINGS.entrySet()) { - String key = entry.getKey(); - String value = "<" + entry.getValue() + ">"; - - message = message.replace("&" + key, value) - .replace("§" + key, value) - .replace("&" + key.toUpperCase(), value) - .replace("§" + key.toUpperCase(), value); - } - return message; - } } diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/MiniMessageColorUtils.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/MiniMessageColorUtils.java new file mode 100644 index 00000000..a3fb90e0 --- /dev/null +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/MiniMessageColorUtils.java @@ -0,0 +1,91 @@ +package fr.maxlego08.menu.hooks; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MiniMessageColorUtils { + private static final Pattern LEGACY_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}"); + private static final Pattern HEX_SHORT_PATTERN = Pattern.compile("(? COLORS_MAPPINGS = Map.ofEntries( + Map.entry("0", "black"), + Map.entry("1", "dark_blue"), + Map.entry("2", "dark_green"), + Map.entry("3", "dark_aqua"), + Map.entry("4", "dark_red"), + Map.entry("5", "dark_purple"), + Map.entry("6", "gold"), + Map.entry("7", "gray"), + Map.entry("8", "dark_gray"), + Map.entry("9", "blue"), + Map.entry("a", "green"), + Map.entry("b", "aqua"), + Map.entry("c", "red"), + Map.entry("d", "light_purple"), + Map.entry("e", "yellow"), + Map.entry("f", "white"), + Map.entry("k", "obfuscated"), + Map.entry("l", "bold"), + Map.entry("m", "strikethrough"), + Map.entry("n", "underlined"), + Map.entry("o", "italic"), + Map.entry("r", "reset") + ); + + protected String colorMiniMessage(String message) { + + String newMessage = message; + + // §x§r§g§b§2§f§3 → <#rgb2f3> + newMessage = convertLegacyHex(newMessage); + // &#a1b2c3 → <#a1b2c3> + newMessage = convertShorLegacyHex(newMessage); + // #a1b2c3 → <#a1b2c3> + newMessage = newMessage.replaceAll("(?"); + + // &a → , §c → , etc. + newMessage = replaceLegacyColors(newMessage); + + return newMessage; + } + + private @NotNull String convertLegacyHex(String message) { + Matcher matcher = LEGACY_HEX_PATTERN.matcher(message); + StringBuilder sb = new StringBuilder(); + + while (matcher.find()) { + String hex = matcher.group().replaceAll("§x|§", ""); + matcher.appendReplacement(sb, "<#" + hex + ">"); + } + + matcher.appendTail(sb); + return sb.toString(); + } + + private @NotNull String convertShorLegacyHex(String message) { + Matcher matcher = HEX_SHORT_PATTERN.matcher(message); + StringBuilder sb = new StringBuilder(); + + while (matcher.find()) { + matcher.appendReplacement(sb, "<#" + matcher.group(2) + ">"); + } + + matcher.appendTail(sb); + return sb.toString(); + } + + private String replaceLegacyColors(String message) { + for (var entry : this.COLORS_MAPPINGS.entrySet()) { + String key = entry.getKey(); + String value = "<" + entry.getValue() + ">"; + + message = message.replace("&" + key, value) + .replace("§" + key, value) + .replace("&" + key.toUpperCase(), value) + .replace("§" + key.toUpperCase(), value); + } + return message; + } +} diff --git a/Hooks/Paper/src/test/java/fr/maxlego08/menu/test/ColorUtilsTest.java b/Hooks/Paper/src/test/java/fr/maxlego08/menu/test/ColorUtilsTest.java new file mode 100644 index 00000000..20aa65ab --- /dev/null +++ b/Hooks/Paper/src/test/java/fr/maxlego08/menu/test/ColorUtilsTest.java @@ -0,0 +1,441 @@ +package fr.maxlego08.menu.test; + +import fr.maxlego08.menu.hooks.MiniMessageColorUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ColorUtilsTest extends MiniMessageColorUtils { + + // ======================================== + // Legacy Hex Pattern Tests (§x§r§g§b§2§f§3) + // ======================================== + + @Test + @DisplayName("Convert legacy hex format to MiniMessage format") + void testLegacyHexConversion() { + String input = "§x§a§1§b§2§c§3Hello"; + String expected = "<#a1b2c3>Hello"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert multiple legacy hex colors") + void testMultipleLegacyHexConversion() { + String input = "§x§f§f§0§0§0§0Red§x§0§0§f§f§0§0Blue"; + String expected = "<#ff0000>Red<#00ff00>Blue"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert legacy hex with uppercase letters") + void testLegacyHexUppercase() { + String input = "§x§A§B§C§D§E§FText"; + String expected = "<#ABCDEF>Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert legacy hex with mixed case") + void testLegacyHexMixedCase() { + String input = "§x§a§B§c§D§e§FText"; + String expected = "<#aBcDeF>Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + // ======================================== + // Short Legacy Hex Pattern Tests (&#a1b2c3) + // ======================================== + + @Test + @DisplayName("Convert short legacy hex format with ampersand") + void testShortLegacyHexConversion() { + String input = "7ff55Hello"; + String expected = "<#55ff55>Hello"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert multiple short legacy hex colors") + void testMultipleShortLegacyHexConversion() { + String input = "&#ff0000Red�ff00Green�ffBlue"; + String expected = "<#ff0000>Red<#00ff00>Green<#0000ff>Blue"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert short legacy hex with uppercase") + void testShortLegacyHexUppercase() { + String input = "&#ABCDEF Text"; + String expected = "<#ABCDEF> Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Test original example from existing test") + void testColorParsing() { + String coloredMessage = colorMiniMessage("7ff55&lAAA7ff8e&lBBB7ffc6&lCCC7ffff&lDDD"); + Assertions.assertEquals("<#55ff55>AAA<#55ff8e>BBB<#55ffc6>CCC<#55ffff>DDD", coloredMessage); + } + + // ======================================== + // Simple Hex Pattern Tests (#a1b2c3) + // ======================================== + + @Test + @DisplayName("Convert simple hex format without prefix") + void testSimpleHexConversion() { + String input = "#55ff55Hello"; + String expected = "<#55ff55>Hello"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert multiple simple hex colors") + void testMultipleSimpleHexConversion() { + String input = "#ff0000Red#00ff00Green#0000ffBlue"; + String expected = "<#ff0000>Red<#00ff00>Green<#0000ff>Blue"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Simple hex should not convert when preceded by <") + void testSimpleHexNoConversionAfterLessThan() { + String input = "<#55ff55Hello"; + String expected = "<#55ff55Hello"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Simple hex should not convert when preceded by &") + void testSimpleHexNoConversionAfterAmpersand() { + // This is already handled by &#pattern, so plain # after & should convert + String input = "Test#55ff55Hello"; + String expected = "Test<#55ff55>Hello"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + // ======================================== + // Legacy Color Code Tests (&a, §c, etc.) + // ======================================== + + @Test + @DisplayName("Convert ampersand color codes to MiniMessage") + void testAmpersandColorCodes() { + String input = "&aGreen&cRed&9Blue"; + String expected = "GreenRedBlue"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert section symbol color codes to MiniMessage") + void testSectionColorCodes() { + String input = "§aGreen§cRed§9Blue"; + String expected = "GreenRedBlue"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert all color codes (0-9, a-f)") + void testAllColorCodes() { + String input = "&0&1&2&3&4&5&6&7&8&9&a&b&c&d&e&f"; + String expected = ""; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert uppercase color codes") + void testUppercaseColorCodes() { + String input = "&AHello&CWorld"; + String expected = "HelloWorld"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert mixed case color codes") + void testMixedCaseColorCodes() { + String input = "&aHello&CWorld&Etest"; + String expected = "HelloWorldtest"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + // ======================================== + // Format Code Tests (bold, italic, etc.) + // ======================================== + + @Test + @DisplayName("Convert bold format code") + void testBoldFormatCode() { + String input = "&lBold Text"; + String expected = "Bold Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert italic format code") + void testItalicFormatCode() { + String input = "&oItalic Text"; + String expected = "Italic Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert underlined format code") + void testUnderlinedFormatCode() { + String input = "&nUnderlined Text"; + String expected = "Underlined Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert strikethrough format code") + void testStrikethroughFormatCode() { + String input = "&mStrikethrough Text"; + String expected = "Strikethrough Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert obfuscated format code") + void testObfuscatedFormatCode() { + String input = "&kObfuscated Text"; + String expected = "Obfuscated Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert reset format code") + void testResetFormatCode() { + String input = "&rReset Text"; + String expected = "Reset Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert all format codes") + void testAllFormatCodes() { + String input = "&k&l&m&n&o&r"; + String expected = ""; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert format codes with section symbol") + void testSectionFormatCodes() { + String input = "§lBold§oItalic§nUnderlined"; + String expected = "BoldItalicUnderlined"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Convert uppercase format codes") + void testUppercaseFormatCodes() { + String input = "&LBold&OItalic"; + String expected = "BoldItalic"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + // ======================================== + // Combined Pattern Tests + // ======================================== + + @Test + @DisplayName("Combine hex color with format codes") + void testHexWithFormatCodes() { + String input = "&#ff0000&lBold Red"; + String expected = "<#ff0000>Bold Red"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Combine legacy color with format codes") + void testLegacyColorWithFormatCodes() { + String input = "&a&lBold Green&r&cRed"; + String expected = "Bold GreenRed"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Combine all three hex patterns") + void testAllHexPatterns() { + String input = "§x§f§f§0§0§0§0A�ff00B#0000ffC"; + String expected = "<#ff0000>A<#00ff00>B<#0000ff>C"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Complex combination with all patterns") + void testComplexCombination() { + String input = "&a&lGreen Bold §x§f§f§0§0§0§0�ff00Hex&r#0000ff&oBlue Italic"; + String expected = "Green Bold <#ff0000><#00ff00>Hex<#0000ff>Blue Italic"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Mix section symbol and ampersand") + void testMixedSymbols() { + String input = "&aAmpersand§cSection&lBold§oItalic"; + String expected = "AmpersandSectionBoldItalic"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + // ======================================== + // Edge Cases and Special Scenarios + // ======================================== + + @Test + @DisplayName("Empty string should return empty") + void testEmptyString() { + String input = ""; + String expected = ""; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("String without color codes should remain unchanged") + void testNoColorCodes() { + String input = "Plain text without colors"; + String expected = "Plain text without colors"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Invalid hex code should not be converted") + void testInvalidHexCode() { + String input = "&#gghhii Invalid"; + String expected = "&#gghhii Invalid"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Incomplete hex code should not be converted") + void testIncompleteHexCode() { + String input = "&#ff00 Incomplete"; + String expected = "&#ff00 Incomplete"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Already converted MiniMessage should not double convert") + void testAlreadyConverted() { + String input = "<#ff0000>Already converted"; + String expected = "<#ff0000>Already converted"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Multiple spaces between color codes") + void testMultipleSpaces() { + String input = "&a Green &c Red"; + String expected = " Green Red"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Newlines with color codes") + void testNewlines() { + String input = "&aGreen\n&cRed\n&9Blue"; + String expected = "Green\nRed\nBlue"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Consecutive color codes") + void testConsecutiveColorCodes() { + String input = "&a&l&n&oText"; + String expected = "Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Color codes at end of string") + void testColorCodesAtEnd() { + String input = "Text&a&l"; + String expected = "Text"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Short hex at start of string") + void testShortHexAtStart() { + String input = "&#ff0000Start"; + String expected = "<#ff0000>Start"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Short hex at end of string") + void testShortHexAtEnd() { + String input = "End&#ff0000"; + String expected = "End<#ff0000>"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Special characters with color codes") + void testSpecialCharacters() { + String input = "&aHello!@#$%^&*()_+-=[]{}|;':\",./<>?"; + String expected = "Hello!@#$%^&*()_+-=[]{}|;':\",./<>?"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Unicode characters with color codes") + void testUnicodeCharacters() { + String input = "&aHéllo Wörld 你好 مرحبا"; + String expected = "Héllo Wörld 你好 مرحبا"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Very long string with multiple colors") + void testLongString() { + StringBuilder input = new StringBuilder(); + StringBuilder expected = new StringBuilder(); + for (int i = 0; i < 100; i++) { + input.append("&a").append(i).append(" "); + expected.append("").append(i).append(" "); + } + Assertions.assertEquals(expected.toString(), colorMiniMessage(input.toString())); + } + + @Test + @DisplayName("Only color codes without text") + void testOnlyColorCodes() { + String input = "&a&l&n&#ff0000§x§0§0§f§f§0§0"; + String expected = "<#ff0000><#00ff00>"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Real world example - gradient text") + void testGradientText() { + String input = "&#ff0000R&#ff3300a&#ff6600i&#ff9900n&#ffcc00b&#ffff00o&#ccff00w"; + String expected = "<#ff0000>R<#ff3300>a<#ff6600>i<#ff9900>n<#ffcc00>b<#ffff00>o<#ccff00>w"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Real world example - server MOTD") + void testServerMOTD() { + String input = "7ff55&l✦ 7ff8e&lMY SERVER 7ffc6&l✦\n&7Join us for fun!"; + String expected = "<#55ff55>✦ <#55ff8e>MY SERVER <#55ffc6>✦\nJoin us for fun!"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Real world example - chat prefix") + void testChatPrefix() { + String input = "&#ff0000[&#ff9900Admin&#ff0000]&r &aPlayerName&f: Message"; + String expected = "<#ff0000>[<#ff9900>Admin<#ff0000>] PlayerName: Message"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } + + @Test + @DisplayName("Preserve existing MiniMessage color tags") + void testMiniMessageColorTagsPreserved() { + String input = "This is a test!"; + String expected = "This is a test!"; + Assertions.assertEquals(expected, colorMiniMessage(input)); + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 15b3d71f..541b8b44 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -85,6 +85,10 @@ allprojects { } } + tasks.test { + useJUnitPlatform() + } + dependencies { if (project.name != "Paper") { compileOnly("org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT") @@ -98,6 +102,10 @@ allprojects { implementation("com.github.cryptomorin:XSeries:13.3.0") implementation("net.objecthunter:exp4j:0.4.8") + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } }