diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRow.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRow.java index 2e66ecb..a552fd0 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRow.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRow.java @@ -197,6 +197,8 @@ public record GuiBatchRunResultRow( *
* Wenn {@code resetPending} den Wert {@code true} hat, wird unabhängig vom * eigentlichen Status das Reset-Icon zurückgegeben. + *
+ * Die Icon-Werte stammen aus {@link ProcessingStatusPresentation}. * * @return das entsprechende Status-Zeichen */ @@ -204,13 +206,7 @@ public record GuiBatchRunResultRow( if (resetPending) { return RESET_PENDING_ICON; } - return switch (status) { - case SUCCESS -> "✓"; // ✓ CHECK MARK - case FAILED_RETRYABLE -> "↻"; // ↻ CLOCKWISE OPEN CIRCLE ARROW - case FAILED_PERMANENT -> "×"; // × MULTIPLICATION SIGN - case SKIPPED_ALREADY_PROCESSED -> "≡"; // ≡ IDENTICAL TO - case SKIPPED_FINAL_FAILURE -> "⊘"; // ⊘ CIRCLED DIVISION SLASH - }; + return ProcessingStatusPresentation.iconFor(status); } /** @@ -218,20 +214,36 @@ public record GuiBatchRunResultRow( *
* Wenn {@code resetPending} den Wert {@code true} hat, wird unabhängig vom * eigentlichen Status die Reset-Farbe zurückgegeben. + *
+ * Farbe ist niemals das einzige Unterscheidungsmerkmal – {@link #statusIcon()} und + * {@link #statusTooltip()} beschreiben den Status auch ohne Farbwahrnehmung eindeutig. + * Die Farbwerte stammen aus {@link ProcessingStatusPresentation}. * - * @return die entsprechende CSS-Hex-Farbe (z.B. "#2e7d32") + * @return die entsprechende CSS-Hex-Farbe (z. B. {@code "#2e7d32"}) */ public String statusColor() { if (resetPending) { return "#757575"; // Grau für Reset-pending } - return switch (status) { - case SUCCESS -> "#2e7d32"; // Grün - case FAILED_RETRYABLE -> "#d98200"; // Orange - case FAILED_PERMANENT -> "#c62828"; // Rot - case SKIPPED_ALREADY_PROCESSED -> "#1565c0"; // Blau-Grau - case SKIPPED_FINAL_FAILURE -> "#757575"; // Grau - }; + return ProcessingStatusPresentation.cssColorFor(status); + } + + /** + * Gibt den deutschsprachigen Tooltip-Text für den Verarbeitungsstatus dieser Zeile zurück. + *
+ * Wenn {@code resetPending} den Wert {@code true} hat, wird ein Tooltip für den + * Reset-Zustand zurückgegeben. + *
+ * Der Tooltip-Text beschreibt den Status vollständig ohne Farbe. Die Texte stammen + * aus {@link ProcessingStatusPresentation}. + * + * @return der Tooltip-Text; nie leer + */ + public String statusTooltip() { + if (resetPending) { + return RESET_PENDING_LABEL; + } + return ProcessingStatusPresentation.tooltipFor(status); } /** @@ -249,7 +261,7 @@ public record GuiBatchRunResultRow( return switch (status) { case SUCCESS -> "Erfolgreich"; case FAILED_RETRYABLE -> "Fehlgeschlagen (wiederholbar)"; - case FAILED_PERMANENT -> "Fehlgeschlagen (permanent)"; + case FAILED_PERMANENT -> "Fehlgeschlagen (dauerhaft)"; case SKIPPED_ALREADY_PROCESSED -> "Übersprungen (bereits verarbeitet)"; case SKIPPED_FINAL_FAILURE -> "Übersprungen (endgültig fehlgeschlagen)"; }; diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java index eb4c109..c2ed63e 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java @@ -67,6 +67,7 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.control.TextArea; +import javafx.scene.control.Tooltip; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; @@ -605,6 +606,7 @@ public final class GuiBatchRunTab { if (empty || icon == null) { setText(null); setStyle(null); + setTooltip(null); return; } setText(icon); @@ -612,9 +614,15 @@ public final class GuiBatchRunTab { GuiBatchRunResultRow data = tableRow != null ? tableRow.getItem() : null; if (data != null && data.resetPending()) { setStyle("-fx-text-fill: #1565c0; -fx-alignment: CENTER; -fx-font-size: 14;"); - } else { - String color = data != null ? statusColor(data.status()) : "#000000"; + setTooltip(new Tooltip(data.statusTooltip())); + } else if (data != null) { + // Farbe aus zentralem Mapping – nie alleiniges Unterscheidungsmerkmal + String color = ProcessingStatusPresentation.cssColorFor(data.status()); setStyle("-fx-text-fill: " + color + "; -fx-alignment: CENTER; -fx-font-size: 14;"); + setTooltip(new Tooltip(data.statusTooltip())); + } else { + setStyle("-fx-alignment: CENTER; -fx-font-size: 14;"); + setTooltip(null); } } }); @@ -1419,15 +1427,7 @@ public final class GuiBatchRunTab { // Statische Helfer // ------------------------------------------------------------------------- - private static String statusColor(DocumentCompletionStatus status) { - return switch (status) { - case SUCCESS -> "#2e7d32"; - case FAILED_RETRYABLE -> "#e65100"; - case FAILED_PERMANENT -> "#c62828"; - case SKIPPED_ALREADY_PROCESSED -> "#1565c0"; - case SKIPPED_FINAL_FAILURE -> "#757575"; - }; - } + // statusColor() wurde zugunsten von ProcessingStatusPresentation.cssColorFor() entfernt. private static String formatDuration(Duration duration) { double seconds = duration.toMillis() / 1000.0; @@ -1475,6 +1475,14 @@ public final class GuiBatchRunTab { "Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")); return builder.toString(); } + if (row.status() == DocumentCompletionStatus.FAILED_PERMANENT) { + // Erweiterter Erkl\u00e4rungstext gem\u00e4\u00df Spezifikation #51 \u2013 dauerhaft fehlgeschlagen + builder.append('\n').append(ProcessingStatusPresentation.DETAIL_TEXT_FAILED_PERMANENT); + row.aiFailureMessage().ifPresent(msg -> + builder.append("\n\nFehlerdetail: ") + .append(AiFailureMessageTranslator.translate(msg))); + return builder.toString(); + } row.effectiveFileName() .ifPresent(name -> builder.append("Neuer Dateiname: ").append(name).append('\n')); row.resolvedDate() diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/ProcessingStatusPresentation.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/ProcessingStatusPresentation.java new file mode 100644 index 0000000..a83824c --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/ProcessingStatusPresentation.java @@ -0,0 +1,257 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun; + +import java.util.Objects; + +import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus; + +/** + * Zentrale Mapping-Klasse für die visuelle Darstellung von Verarbeitungsstatus in der GUI. + *
+ * Diese Klasse ist die einzige autoritative Quelle für Status-Icons, CSS-Farben, + * Tooltip-Texte und Summary-Kategorielabels aller {@link DocumentCompletionStatus}-Werte. + * Alle Anzeigeorte im GUI-Adapter (Ergebnistabelle, Detailbereich, Summary-Banner) + * beziehen ihre Darstellungsinformationen ausschließlich über diese Klasse. + *
+ * Farbe ist niemals das einzige Unterscheidungsmerkmal: Icon und Tooltip-Text beschreiben + * den Status vollständig auch ohne Farbwahrnehmung. + *
+ * Diese Klasse enthält keine JavaFX-Typen; sie ist rein datenhaltend und zustandslos. + * Alle Methoden sind statisch. + */ +public final class ProcessingStatusPresentation { + + // ------------------------------------------------------------------------- + // Icons (Unicode-Zeichen, zuverlässig darstellbar unter Windows 10+) + // ------------------------------------------------------------------------- + + /** Icon für {@link DocumentCompletionStatus#SUCCESS}. */ + public static final String ICON_SUCCESS = "✓"; // CHECK MARK + + /** Icon für {@link DocumentCompletionStatus#FAILED_RETRYABLE}. */ + public static final String ICON_FAILED_RETRYABLE = "↻"; // CLOCKWISE OPEN CIRCLE ARROW + + /** Icon für {@link DocumentCompletionStatus#FAILED_PERMANENT}. */ + public static final String ICON_FAILED_PERMANENT = "×"; // MULTIPLICATION SIGN + + /** Icon für {@link DocumentCompletionStatus#SKIPPED_ALREADY_PROCESSED}. */ + public static final String ICON_SKIPPED_ALREADY_PROCESSED = "≡"; // IDENTICAL TO + + /** Icon für {@link DocumentCompletionStatus#SKIPPED_FINAL_FAILURE}. */ + public static final String ICON_SKIPPED_FINAL_FAILURE = "⊘"; // CIRCLED DIVISION SLASH + + // ------------------------------------------------------------------------- + // CSS-Farben (Hex-Strings für JavaFX setStyle) + // ------------------------------------------------------------------------- + + /** CSS-Farbe für {@link DocumentCompletionStatus#SUCCESS}. */ + public static final String COLOR_SUCCESS = "#2e7d32"; // Grün + + /** CSS-Farbe für {@link DocumentCompletionStatus#FAILED_RETRYABLE}. */ + public static final String COLOR_FAILED_RETRYABLE = "#d98200"; // Orange + + /** CSS-Farbe für {@link DocumentCompletionStatus#FAILED_PERMANENT}. */ + public static final String COLOR_FAILED_PERMANENT = "#c62828"; // Rot + + /** CSS-Farbe für {@link DocumentCompletionStatus#SKIPPED_ALREADY_PROCESSED}. */ + public static final String COLOR_SKIPPED_ALREADY_PROCESSED = "#757575"; // Grau + + /** CSS-Farbe für {@link DocumentCompletionStatus#SKIPPED_FINAL_FAILURE}. */ + public static final String COLOR_SKIPPED_FINAL_FAILURE = "#424242"; // Dunkelgrau + + // ------------------------------------------------------------------------- + // Tooltip-Texte (deutsche Benutzertexte, gemäß Spezifikation) + // ------------------------------------------------------------------------- + + /** Tooltip für {@link DocumentCompletionStatus#SUCCESS}. */ + public static final String TOOLTIP_SUCCESS = + "Erfolgreich verarbeitet und umbenannt."; + + /** Tooltip für {@link DocumentCompletionStatus#FAILED_RETRYABLE}. */ + public static final String TOOLTIP_FAILED_RETRYABLE = + "Temporärer Fehler – wird beim nächsten Lauf automatisch erneut versucht."; + + /** Tooltip für {@link DocumentCompletionStatus#FAILED_PERMANENT}. */ + public static final String TOOLTIP_FAILED_PERMANENT = + "Dauerhaft nicht verarbeitbar – z. B. kein Textinhalt (Foto-PDF), Passwortschutz " + + "oder beschädigte Datei. Kein weiterer automatischer Versuch."; + + /** Tooltip für {@link DocumentCompletionStatus#SKIPPED_ALREADY_PROCESSED}. */ + public static final String TOOLTIP_SKIPPED_ALREADY_PROCESSED = + "Übersprungen – wurde bereits in einem früheren Lauf erfolgreich verarbeitet."; + + /** Tooltip für {@link DocumentCompletionStatus#SKIPPED_FINAL_FAILURE}. */ + public static final String TOOLTIP_SKIPPED_FINAL_FAILURE = + "Endgültig übersprungen nach wiederholten Fehlern."; + + // ------------------------------------------------------------------------- + // Detailtext für FAILED_PERMANENT (Erklärung im Detailbereich) + // ------------------------------------------------------------------------- + + /** + * Erweiterter Erklärungstext, der im Detailbereich bei dauerhaft fehlgeschlagenen + * Dokumenten angezeigt wird. + */ + public static final String DETAIL_TEXT_FAILED_PERMANENT = + "Diese Datei kann nicht verarbeitet werden. Mögliche Ursachen: " + + "kein lesbarer Text (z. B. gescanntes Foto ohne OCR), Passwortschutz " + + "oder beschädigte Datei. " + + "Sie können den Status manuell zurücksetzen, wenn Sie die Ursache behoben haben."; + + // ------------------------------------------------------------------------- + // Summary-Kategorielabels + // ------------------------------------------------------------------------- + + /** Summary-Kategorie für {@link DocumentCompletionStatus#SUCCESS}. */ + public static final String SUMMARY_CATEGORY_SUCCESS = "erfolgreich"; + + /** Summary-Kategorie für {@link DocumentCompletionStatus#FAILED_RETRYABLE}. */ + public static final String SUMMARY_CATEGORY_FAILED_RETRYABLE = "wird wiederholt"; + + /** Summary-Kategorie für {@link DocumentCompletionStatus#FAILED_PERMANENT}. */ + public static final String SUMMARY_CATEGORY_FAILED_PERMANENT = "fehlgeschlagen"; + + /** Summary-Kategorie für {@link DocumentCompletionStatus#SKIPPED_ALREADY_PROCESSED}. */ + public static final String SUMMARY_CATEGORY_SKIPPED_ALREADY_PROCESSED = "übersprungen"; + + /** Summary-Kategorie für {@link DocumentCompletionStatus#SKIPPED_FINAL_FAILURE}. */ + public static final String SUMMARY_CATEGORY_SKIPPED_FINAL_FAILURE = "endgültig übersprungen"; + + // ------------------------------------------------------------------------- + // Record-Typ für gebündelte Darstellungsinformationen + // ------------------------------------------------------------------------- + + /** + * Gebündelte visuelle Darstellungsinformationen für einen Verarbeitungsstatus. + * + * @param icon Unicode-Zeichen als Status-Icon; nie leer + * @param cssColor CSS-Hex-Farbe für das Icon, z. B. {@code "#2e7d32"}; nie leer + * @param tooltipText Deutschsprachiger Tooltip-Text; nie leer + * @param summaryCategoryLabel Kategorie-Bezeichnung für das Summary-Banner; nie leer + */ + public record StatusVisuals( + String icon, + String cssColor, + String tooltipText, + String summaryCategoryLabel) { + + /** + * Kompakter Konstruktor zur Pflichtfeld-Validierung. + * + * @throws NullPointerException wenn ein Feld {@code null} ist + * @throws IllegalArgumentException wenn ein String-Feld leer ist + */ + public StatusVisuals { + Objects.requireNonNull(icon, "icon muss gesetzt sein"); + Objects.requireNonNull(cssColor, "cssColor muss gesetzt sein"); + Objects.requireNonNull(tooltipText, "tooltipText muss gesetzt sein"); + Objects.requireNonNull(summaryCategoryLabel, "summaryCategoryLabel muss gesetzt sein"); + if (icon.isBlank()) throw new IllegalArgumentException("icon darf nicht leer sein"); + if (cssColor.isBlank()) throw new IllegalArgumentException("cssColor darf nicht leer sein"); + if (tooltipText.isBlank()) throw new IllegalArgumentException("tooltipText darf nicht leer sein"); + if (summaryCategoryLabel.isBlank()) + throw new IllegalArgumentException("summaryCategoryLabel darf nicht leer sein"); + } + } + + // ------------------------------------------------------------------------- + // Zentrale Mapping-Methoden + // ------------------------------------------------------------------------- + + /** + * Liefert das Status-Icon für den angegebenen Verarbeitungsstatus. + * + * @param status der Verarbeitungsstatus; darf nicht {@code null} sein + * @return das zugehörige Unicode-Zeichen; nie leer + * @throws NullPointerException wenn {@code status} {@code null} ist + */ + public static String iconFor(DocumentCompletionStatus status) { + Objects.requireNonNull(status, "status darf nicht null sein"); + return switch (status) { + case SUCCESS -> ICON_SUCCESS; + case FAILED_RETRYABLE -> ICON_FAILED_RETRYABLE; + case FAILED_PERMANENT -> ICON_FAILED_PERMANENT; + case SKIPPED_ALREADY_PROCESSED -> ICON_SKIPPED_ALREADY_PROCESSED; + case SKIPPED_FINAL_FAILURE -> ICON_SKIPPED_FINAL_FAILURE; + }; + } + + /** + * Liefert die CSS-Hex-Farbe für das Status-Icon des angegebenen Verarbeitungsstatus. + *
+ * Die Farbe ist nie das einzige Unterscheidungsmerkmal – Icon und Tooltip-Text + * beschreiben den Status unabhängig von der Farbe eindeutig. + * + * @param status der Verarbeitungsstatus; darf nicht {@code null} sein + * @return die CSS-Hex-Farbe (z. B. {@code "#2e7d32"}); nie leer + * @throws NullPointerException wenn {@code status} {@code null} ist + */ + public static String cssColorFor(DocumentCompletionStatus status) { + Objects.requireNonNull(status, "status darf nicht null sein"); + return switch (status) { + case SUCCESS -> COLOR_SUCCESS; + case FAILED_RETRYABLE -> COLOR_FAILED_RETRYABLE; + case FAILED_PERMANENT -> COLOR_FAILED_PERMANENT; + case SKIPPED_ALREADY_PROCESSED -> COLOR_SKIPPED_ALREADY_PROCESSED; + case SKIPPED_FINAL_FAILURE -> COLOR_SKIPPED_FINAL_FAILURE; + }; + } + + /** + * Liefert den deutschsprachigen Tooltip-Text für den angegebenen Verarbeitungsstatus. + * + * @param status der Verarbeitungsstatus; darf nicht {@code null} sein + * @return der Tooltip-Text; nie leer + * @throws NullPointerException wenn {@code status} {@code null} ist + */ + public static String tooltipFor(DocumentCompletionStatus status) { + Objects.requireNonNull(status, "status darf nicht null sein"); + return switch (status) { + case SUCCESS -> TOOLTIP_SUCCESS; + case FAILED_RETRYABLE -> TOOLTIP_FAILED_RETRYABLE; + case FAILED_PERMANENT -> TOOLTIP_FAILED_PERMANENT; + case SKIPPED_ALREADY_PROCESSED -> TOOLTIP_SKIPPED_ALREADY_PROCESSED; + case SKIPPED_FINAL_FAILURE -> TOOLTIP_SKIPPED_FINAL_FAILURE; + }; + } + + /** + * Liefert die Summary-Kategorie-Bezeichnung für den angegebenen Verarbeitungsstatus. + * Diese Kategorie wird im Summary-Banner nach einem Lauf angezeigt. + * + * @param status der Verarbeitungsstatus; darf nicht {@code null} sein + * @return die Kategorienbezeichnung; nie leer + * @throws NullPointerException wenn {@code status} {@code null} ist + */ + public static String summaryCategoryFor(DocumentCompletionStatus status) { + Objects.requireNonNull(status, "status darf nicht null sein"); + return switch (status) { + case SUCCESS -> SUMMARY_CATEGORY_SUCCESS; + case FAILED_RETRYABLE -> SUMMARY_CATEGORY_FAILED_RETRYABLE; + case FAILED_PERMANENT -> SUMMARY_CATEGORY_FAILED_PERMANENT; + case SKIPPED_ALREADY_PROCESSED -> SUMMARY_CATEGORY_SKIPPED_ALREADY_PROCESSED; + case SKIPPED_FINAL_FAILURE -> SUMMARY_CATEGORY_SKIPPED_FINAL_FAILURE; + }; + } + + /** + * Liefert alle gebündelten visuellen Darstellungsinformationen für den angegebenen + * Verarbeitungsstatus in einem einzigen Objekt. + * + * @param status der Verarbeitungsstatus; darf nicht {@code null} sein + * @return ein befülltes {@link StatusVisuals}-Record; nie {@code null} + * @throws NullPointerException wenn {@code status} {@code null} ist + */ + public static StatusVisuals visualsFor(DocumentCompletionStatus status) { + Objects.requireNonNull(status, "status darf nicht null sein"); + return new StatusVisuals( + iconFor(status), + cssColorFor(status), + tooltipFor(status), + summaryCategoryFor(status)); + } + + /** Nicht instanziierbar – reine Utility-Klasse. */ + private ProcessingStatusPresentation() { + throw new UnsupportedOperationException("Nicht instanziierbar"); + } +} diff --git a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRowTest.java b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRowTest.java index 4b26f74..c7b7aec 100644 --- a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRowTest.java +++ b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunResultRowTest.java @@ -188,6 +188,50 @@ class GuiBatchRunResultRowTest { } } + // ------------------------------------------------------------------------- + // statusTooltip + // ------------------------------------------------------------------------- + + @Test + void statusTooltip_success_isNonBlank() { + assertFalse(row(DocumentCompletionStatus.SUCCESS).statusTooltip().isBlank()); + } + + @Test + void statusTooltip_failedRetryable_isNonBlank() { + assertFalse(row(DocumentCompletionStatus.FAILED_RETRYABLE).statusTooltip().isBlank()); + } + + @Test + void statusTooltip_failedPermanent_isNonBlank() { + assertFalse(row(DocumentCompletionStatus.FAILED_PERMANENT).statusTooltip().isBlank()); + } + + @Test + void statusTooltip_failedRetryable_and_failedPermanent_areDifferent() { + String retryable = row(DocumentCompletionStatus.FAILED_RETRYABLE).statusTooltip(); + String permanent = row(DocumentCompletionStatus.FAILED_PERMANENT).statusTooltip(); + assertFalse(retryable.equals(permanent), + "FAILED_RETRYABLE und FAILED_PERMANENT müssen unterschiedliche Tooltips haben"); + } + + @Test + void statusTooltip_allStatuses_delegatesToProcessingStatusPresentation() { + for (DocumentCompletionStatus status : DocumentCompletionStatus.values()) { + String rowTooltip = row(status).statusTooltip(); + String expectedTooltip = ProcessingStatusPresentation.tooltipFor(status); + assertEquals(expectedTooltip, rowTooltip, + "statusTooltip() soll Wert von ProcessingStatusPresentation liefern für " + status); + } + } + + @Test + void statusTooltip_resetPending_returnsResetLabel() { + GuiBatchRunResultRow marker = GuiBatchRunResultRow.resetMarker( + row(DocumentCompletionStatus.SUCCESS)); + assertEquals(GuiBatchRunResultRow.RESET_PENDING_LABEL, marker.statusTooltip()); + } + // ------------------------------------------------------------------------- // aiFailureMessage // ------------------------------------------------------------------------- diff --git a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/ProcessingStatusPresentationTest.java b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/ProcessingStatusPresentationTest.java new file mode 100644 index 0000000..ec4bb9b --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/ProcessingStatusPresentationTest.java @@ -0,0 +1,272 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus; +import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.ProcessingStatusPresentation.StatusVisuals; + +/** + * Unit-Tests für {@link ProcessingStatusPresentation}. + *
+ * Prüft, dass alle {@link DocumentCompletionStatus}-Werte korrekte Icons, Farben,
+ * Tooltip-Texte und Summary-Kategorielabels liefern und dass keine zwei Status
+ * dasselbe Icon teilen.
+ */
+class ProcessingStatusPresentationTest {
+
+ // -------------------------------------------------------------------------
+ // iconFor
+ // -------------------------------------------------------------------------
+
+ @Test
+ void iconFor_success_isCheckMark() {
+ assertEquals(ProcessingStatusPresentation.ICON_SUCCESS,
+ ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SUCCESS));
+ }
+
+ @Test
+ void iconFor_failedRetryable_isClockwiseArrow() {
+ assertEquals(ProcessingStatusPresentation.ICON_FAILED_RETRYABLE,
+ ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_RETRYABLE));
+ }
+
+ @Test
+ void iconFor_failedPermanent_isMultiplicationSign() {
+ assertEquals(ProcessingStatusPresentation.ICON_FAILED_PERMANENT,
+ ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_PERMANENT));
+ }
+
+ @Test
+ void iconFor_skippedAlreadyProcessed_isIdenticalTo() {
+ assertEquals(ProcessingStatusPresentation.ICON_SKIPPED_ALREADY_PROCESSED,
+ ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED));
+ }
+
+ @Test
+ void iconFor_skippedFinalFailure_isCircledDivisionSlash() {
+ assertEquals(ProcessingStatusPresentation.ICON_SKIPPED_FINAL_FAILURE,
+ ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_FINAL_FAILURE));
+ }
+
+ @Test
+ void iconFor_null_throws() {
+ assertThrows(NullPointerException.class,
+ () -> ProcessingStatusPresentation.iconFor(null));
+ }
+
+ @Test
+ void icons_areAllDistinct() {
+ Set