#51: Einheitliche Status-Darstellung mit Icon, Farbe und Tooltip
Neue zentrale Klasse ProcessingStatusPresentation als einzige autoritative Quelle fuer Icons, CSS-Farben, Tooltip-Texte und Summary-Kategorielabels aller DocumentCompletionStatus-Werte. GuiBatchRunResultRow delegiert statusIcon() und statusColor() an diese Klasse und stellt neue Methode statusTooltip() bereit. In GuiBatchRunTab erhalten Status-Icons Tooltips per CellFactory; die duplizierte private statusColor()-Methode entfaellt. Fuer FAILED_PERMANENT wird im Detailbereich ein erweiterter Erklaerungstext gemaess Spezifikation #51 angezeigt. Unit-Tests fuer ProcessingStatusPresentation (alle Status, Eindeutigkeit, korrekte Mapping-Werte) und statusTooltip() in GuiBatchRunResultRowTest ergaenzt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+28
-16
@@ -197,6 +197,8 @@ public record GuiBatchRunResultRow(
|
||||
* <p>
|
||||
* Wenn {@code resetPending} den Wert {@code true} hat, wird unabhängig vom
|
||||
* eigentlichen Status das Reset-Icon zurückgegeben.
|
||||
* <p>
|
||||
* 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(
|
||||
* <p>
|
||||
* Wenn {@code resetPending} den Wert {@code true} hat, wird unabhängig vom
|
||||
* eigentlichen Status die Reset-Farbe zurückgegeben.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Wenn {@code resetPending} den Wert {@code true} hat, wird ein Tooltip für den
|
||||
* Reset-Zustand zurückgegeben.
|
||||
* <p>
|
||||
* 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)";
|
||||
};
|
||||
|
||||
+19
-11
@@ -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()
|
||||
|
||||
+257
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Farbe ist niemals das einzige Unterscheidungsmerkmal: Icon und Tooltip-Text beschreiben
|
||||
* den Status vollständig auch ohne Farbwahrnehmung.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
+44
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
+272
@@ -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}.
|
||||
* <p>
|
||||
* 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<String> icons = new HashSet<>();
|
||||
for (DocumentCompletionStatus status : DocumentCompletionStatus.values()) {
|
||||
icons.add(ProcessingStatusPresentation.iconFor(status));
|
||||
}
|
||||
assertEquals(DocumentCompletionStatus.values().length, icons.size(),
|
||||
"Jeder Status muss ein eindeutiges Icon haben");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// cssColorFor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(DocumentCompletionStatus.class)
|
||||
void cssColorFor_allStatuses_returnsNonBlankHexColor(DocumentCompletionStatus status) {
|
||||
String color = ProcessingStatusPresentation.cssColorFor(status);
|
||||
assertAll(
|
||||
() -> assertNotNull(color, "Farbe darf nicht null sein für " + status),
|
||||
() -> assertFalse(color.isBlank(), "Farbe darf nicht leer sein für " + status),
|
||||
() -> assertFalse(color.isEmpty(), "Farbe darf nicht leer sein für " + status)
|
||||
);
|
||||
// Farbe muss im CSS-Hex-Format beginnen (#)
|
||||
assertFalse(color.isBlank());
|
||||
assertEquals('#', color.charAt(0), "CSS-Farbe muss mit # beginnen für " + status);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cssColorFor_null_throws() {
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> ProcessingStatusPresentation.cssColorFor(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedRetryable_and_failedPermanent_haveDifferentColors() {
|
||||
String orangeColor = ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_RETRYABLE);
|
||||
String redColor = ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_PERMANENT);
|
||||
assertFalse(orangeColor.equals(redColor),
|
||||
"FAILED_RETRYABLE (Orange) und FAILED_PERMANENT (Rot) müssen unterschiedliche Farben haben");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// tooltipFor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(DocumentCompletionStatus.class)
|
||||
void tooltipFor_allStatuses_returnsNonBlankText(DocumentCompletionStatus status) {
|
||||
String tooltip = ProcessingStatusPresentation.tooltipFor(status);
|
||||
assertNotNull(tooltip, "Tooltip darf nicht null sein für " + status);
|
||||
assertFalse(tooltip.isBlank(), "Tooltip darf nicht leer sein für " + status);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooltipFor_null_throws() {
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> ProcessingStatusPresentation.tooltipFor(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooltipFor_failedRetryable_containsWiederholung() {
|
||||
String tooltip = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_RETRYABLE);
|
||||
assertFalse(tooltip.isBlank());
|
||||
// Tooltip muss die Retry-Semantik kommunizieren
|
||||
assertFalse(tooltip.equals(ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_PERMANENT)),
|
||||
"FAILED_RETRYABLE und FAILED_PERMANENT müssen unterschiedliche Tooltips haben");
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooltipFor_failedPermanent_containsKeinWeitererVersuch() {
|
||||
String tooltip = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_PERMANENT);
|
||||
// Tooltip für FAILED_PERMANENT muss kommunizieren, dass kein weiterer automatischer Versuch folgt
|
||||
assertFalse(tooltip.isBlank());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooltips_areAllDistinct() {
|
||||
Set<String> tooltips = new HashSet<>();
|
||||
for (DocumentCompletionStatus status : DocumentCompletionStatus.values()) {
|
||||
tooltips.add(ProcessingStatusPresentation.tooltipFor(status));
|
||||
}
|
||||
assertEquals(DocumentCompletionStatus.values().length, tooltips.size(),
|
||||
"Jeder Status muss einen eindeutigen Tooltip haben");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// summaryCategoryFor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(DocumentCompletionStatus.class)
|
||||
void summaryCategoryFor_allStatuses_returnsNonBlankLabel(DocumentCompletionStatus status) {
|
||||
String category = ProcessingStatusPresentation.summaryCategoryFor(status);
|
||||
assertNotNull(category, "Summary-Kategorie darf nicht null sein für " + status);
|
||||
assertFalse(category.isBlank(), "Summary-Kategorie darf nicht leer sein für " + status);
|
||||
}
|
||||
|
||||
@Test
|
||||
void summaryCategoryFor_null_throws() {
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> ProcessingStatusPresentation.summaryCategoryFor(null));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// visualsFor (gebündelt)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(DocumentCompletionStatus.class)
|
||||
void visualsFor_allStatuses_returnsConsistentRecord(DocumentCompletionStatus status) {
|
||||
StatusVisuals visuals = ProcessingStatusPresentation.visualsFor(status);
|
||||
assertAll(
|
||||
() -> assertNotNull(visuals, "StatusVisuals darf nicht null sein für " + status),
|
||||
() -> assertEquals(ProcessingStatusPresentation.iconFor(status), visuals.icon()),
|
||||
() -> assertEquals(ProcessingStatusPresentation.cssColorFor(status), visuals.cssColor()),
|
||||
() -> assertEquals(ProcessingStatusPresentation.tooltipFor(status), visuals.tooltipText()),
|
||||
() -> assertEquals(ProcessingStatusPresentation.summaryCategoryFor(status),
|
||||
visuals.summaryCategoryLabel())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void visualsFor_null_throws() {
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> ProcessingStatusPresentation.visualsFor(null));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Spezifische Status-Mapping-Werte (gemäß Spezifikation)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void success_mapping_correctValues() {
|
||||
assertAll(
|
||||
() -> assertEquals("✓", ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SUCCESS)),
|
||||
() -> assertEquals("#2e7d32", ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.SUCCESS)),
|
||||
() -> assertEquals("erfolgreich",
|
||||
ProcessingStatusPresentation.summaryCategoryFor(DocumentCompletionStatus.SUCCESS))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedRetryable_mapping_correctValues() {
|
||||
assertAll(
|
||||
() -> assertEquals("↻", ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_RETRYABLE)),
|
||||
() -> assertEquals("#d98200",
|
||||
ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_RETRYABLE)),
|
||||
() -> assertEquals("wird wiederholt",
|
||||
ProcessingStatusPresentation.summaryCategoryFor(DocumentCompletionStatus.FAILED_RETRYABLE))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedPermanent_mapping_correctValues() {
|
||||
assertAll(
|
||||
() -> assertEquals("×", ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_PERMANENT)),
|
||||
() -> assertEquals("#c62828",
|
||||
ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_PERMANENT)),
|
||||
() -> assertEquals("fehlgeschlagen",
|
||||
ProcessingStatusPresentation.summaryCategoryFor(DocumentCompletionStatus.FAILED_PERMANENT))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skippedAlreadyProcessed_mapping_correctValues() {
|
||||
assertAll(
|
||||
() -> assertEquals("≡",
|
||||
ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED)),
|
||||
() -> assertEquals("übersprungen",
|
||||
ProcessingStatusPresentation.summaryCategoryFor(
|
||||
DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skippedFinalFailure_mapping_correctValues() {
|
||||
assertAll(
|
||||
() -> assertEquals("⊘",
|
||||
ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_FINAL_FAILURE)),
|
||||
() -> assertEquals("endgültig übersprungen",
|
||||
ProcessingStatusPresentation.summaryCategoryFor(
|
||||
DocumentCompletionStatus.SKIPPED_FINAL_FAILURE))
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Farbe ist NICHT einziges Unterscheidungsmerkmal
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void failedRetryable_and_failedPermanent_distinctByIconAndTooltip() {
|
||||
String iconRetryable = ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_RETRYABLE);
|
||||
String iconPermanent = ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_PERMANENT);
|
||||
String tooltipRetryable = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_RETRYABLE);
|
||||
String tooltipPermanent = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_PERMANENT);
|
||||
assertAll(
|
||||
() -> assertFalse(iconRetryable.equals(iconPermanent),
|
||||
"Icons müssen sich unterscheiden"),
|
||||
() -> assertFalse(tooltipRetryable.equals(tooltipPermanent),
|
||||
"Tooltips müssen sich unterscheiden")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user