diff --git a/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/theme/DesignTokenGuardrailTest.java b/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/theme/DesignTokenGuardrailTest.java new file mode 100644 index 0000000..6e7289c --- /dev/null +++ b/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/theme/DesignTokenGuardrailTest.java @@ -0,0 +1,143 @@ +package br.com.cybercore.signer.ui.theme; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Testes de guarda-corpo arquitetural para o sistema de Design Tokens do CyberCore Signer. + * Assegura que nenhuma cor hexadecimal solta seja introduzida fora dos blocos canônicos de tokens, + * garantindo conformidade com o tema escuro/claro e consistência visual rigorosa. + */ +class DesignTokenGuardrailTest { + + private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("#([0-9a-fA-F]{3,8})\\b"); + + @Test + @DisplayName("theme.css não deve conter cores hexadecimais fora dos blocos de tokens (.theme-light e .theme-dark)") + void shouldNotHaveHardcodedHexColorsOutsideTokenDefinitions() throws Exception { + InputStream stream = getClass().getResourceAsStream("/styles/theme.css"); + assertThat(stream).withFailMessage("Arquivo /styles/theme.css não encontrado no classpath!").isNotNull(); + + List violations = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + int lineNumber = 0; + boolean insideTokenBlock = false; + int openBracesInBlock = 0; + + while ((line = reader.readLine()) != null) { + lineNumber++; + String trimmed = line.trim(); + + // Ignora comentários de linha inteira + if (trimmed.startsWith("/*") && trimmed.endsWith("*/")) { + continue; + } + + // Detecta entrada nos blocos de tokens .theme-light e .theme-dark + if (trimmed.contains(".theme-light") || trimmed.contains(".theme-dark")) { + insideTokenBlock = true; + } + + if (insideTokenBlock) { + for (char c : line.toCharArray()) { + if (c == '{') { + openBracesInBlock++; + } else if (c == '}') { + openBracesInBlock--; + if (openBracesInBlock <= 0) { + insideTokenBlock = false; + openBracesInBlock = 0; + } + } + } + continue; // Cores hex dentro do bloco de tokens são permitidas + } + + // Fora dos blocos de tokens: proibir qualquer cor hexadecimal + // Remove comentários inline antes de testar + String codeOnly = line.replaceAll("/\\*.*?\\*/", ""); + Matcher matcher = HEX_COLOR_PATTERN.matcher(codeOnly); + while (matcher.find()) { + violations.add(String.format("Linha %d: '%s' contém cor hexadecimal solta '%s'", + lineNumber, trimmed, matcher.group())); + } + } + } + + assertThat(violations) + .withFailMessage("Foram encontradas cores hexadecimais soltas fora dos blocos de tokens em theme.css:\n%s", + String.join("\n", violations)) + .isEmpty(); + } + + @Test + @DisplayName("theme.css não deve conter seletores legados residuais serpro-*") + void shouldNotContainLegacySerproSelectors() throws Exception { + InputStream stream = getClass().getResourceAsStream("/styles/theme.css"); + assertThat(stream).isNotNull(); + + List legacySelectors = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + int lineNumber = 0; + while ((line = reader.readLine()) != null) { + lineNumber++; + if (line.toLowerCase().contains(".serpro-")) { + legacySelectors.add(String.format("Linha %d: %s", lineNumber, line.trim())); + } + } + } + + assertThat(legacySelectors) + .withFailMessage("Foram encontrados seletores legados .serpro-* em theme.css:\n%s", + String.join("\n", legacySelectors)) + .isEmpty(); + } + + @Test + @DisplayName("theme.css deve definir todos os tokens de cores semânticas primários e de superfície") + void shouldDefineAllRequiredSemanticColorTokens() throws Exception { + InputStream stream = getClass().getResourceAsStream("/styles/theme.css"); + assertThat(stream).isNotNull(); + + String content; + try (stream) { + content = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + + String[] requiredTokens = { + "-fx-color-primary", + "-fx-color-primary-hover", + "-fx-color-surface", + "-fx-color-surface-hover", + "-fx-color-background", + "-fx-color-border", + "-fx-color-text", + "-fx-color-text-muted", + "-fx-color-text-inverse", + "-fx-color-success", + "-fx-color-warning", + "-fx-color-error" + }; + + for (String token : requiredTokens) { + assertThat(content) + .withFailMessage("Token semântico obrigatório ausente em theme.css: %s", token) + .contains(token); + } + } +}