From 0fe53592990e2221226cfc5f333a54a82bedf721 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Thu, 30 Apr 2026 12:13:00 +0200 Subject: [PATCH] #66: Tooltips auf Konfigurationstab, Verarbeitungslauf-Tab und Toolbar Co-Authored-By: Claude Sonnet 4.6 --- .../gui/GuiConfigurationEditorWorkspace.java | 35 +++ .../adapter/in/gui/GuiTooltipTexts.java | 112 ++++++++++ .../in/gui/batchrun/FileNameEditorPane.java | 9 + .../in/gui/editor/GuiModelFieldContainer.java | 22 ++ .../adapter/in/gui/GuiTooltipTextsTest.java | 205 ++++++++++++++++++ 5 files changed, 383 insertions(+) create mode 100644 pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTexts.java create mode 100644 pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTextsTest.java diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java index 9f0b7c5..8d19d6a 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java @@ -72,6 +72,8 @@ import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TextField; import javafx.scene.control.TitledPane; +import javafx.scene.control.Tooltip; +import javafx.util.Duration; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.scene.input.KeyCode; @@ -1277,6 +1279,12 @@ public final class GuiConfigurationEditorWorkspace { } private void configureActionBar() { + // Tooltips für Toolbar-Buttons gemäß Spezifikation + applyTooltip(newButton, GuiTooltipTexts.TOOLBAR_NEU); + applyTooltip(openButton, GuiTooltipTexts.TOOLBAR_OEFFNEN); + applyTooltip(saveButton, GuiTooltipTexts.TOOLBAR_SPEICHERN); + applyTooltip(saveAsButton, GuiTooltipTexts.TOOLBAR_SPEICHERN_UNTER); + HBox actionBar = new HBox(10, newButton, openButton, saveButton, saveAsButton); actionBar.setAlignment(Pos.CENTER_LEFT); actionBar.setPadding(new Insets(16, 0, 0, 0)); @@ -1364,12 +1372,14 @@ public final class GuiConfigurationEditorWorkspace { TextField sourceFolderField = boundTextField( editorState.values().sourceFolder(), val -> updateValues(editorState.values().withSourceFolder(val))); + applyTooltip(sourceFolderField, GuiTooltipTexts.PFADE_QUELLORDNER); Label sourceFolderErrorLabel = createFieldErrorLabel(); fieldErrorLabels.put("source.folder", sourceFolderErrorLabel); TextField targetFolderField = boundTextField( editorState.values().targetFolder(), val -> updateValues(editorState.values().withTargetFolder(val))); + applyTooltip(targetFolderField, GuiTooltipTexts.PFADE_ZIELORDNER); Label targetFolderErrorLabel = createFieldErrorLabel(); fieldErrorLabels.put("target.folder", targetFolderErrorLabel); @@ -1394,12 +1404,14 @@ public final class GuiConfigurationEditorWorkspace { TextField sqliteField = boundTextField( editorState.values().sqliteFile(), val -> updateValues(editorState.values().withSqliteFile(val))); + applyTooltip(sqliteField, GuiTooltipTexts.PFADE_SQLITE); Label sqliteErrorLabel = createFieldErrorLabel(); fieldErrorLabels.put("sqlite.file", sqliteErrorLabel); TextField promptField = boundTextField( editorState.values().promptTemplateFile(), val -> updateValues(editorState.values().withPromptTemplateFile(val))); + applyTooltip(promptField, GuiTooltipTexts.PFADE_PROMPT); Label promptErrorLabel = createFieldErrorLabel(); fieldErrorLabels.put("prompt.template.file", promptErrorLabel); @@ -1487,6 +1499,7 @@ public final class GuiConfigurationEditorWorkspace { providerComboBox.setConverter(new AiProviderFamilyStringConverter()); providerComboBox.getItems().addAll(AiProviderFamily.CLAUDE, AiProviderFamily.OPENAI_COMPATIBLE); providerComboBox.setValue(initialProvider); + applyTooltip(providerComboBox, GuiTooltipTexts.PROVIDER_COMBOBOX); // --- "Modelle neu laden" button --- Button reloadModelsButton = new Button("Modelle neu laden"); @@ -1753,6 +1766,7 @@ public final class GuiConfigurationEditorWorkspace { pState.model(), val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState( pState2.baseUrl(), val, pState2.timeoutSeconds(), pState2.apiKey()))); + modelContainer.applyTooltip(GuiTooltipTexts.PROVIDER_MODELL); modelFieldContainers.put(family, modelContainer); modelCatalogCoordinator.registerFieldContainer(family, modelContainer); Label modelError = createFieldErrorLabel(); @@ -1846,12 +1860,14 @@ public final class GuiConfigurationEditorWorkspace { TextField maxPagesField = boundTextField( editorState.values().maxPages(), val -> updateValues(editorState.values().withMaxPages(val))); + applyTooltip(maxPagesField, GuiTooltipTexts.LIMITS_MAX_PAGES); grid.add(new Label("Max. Seiten:"), 0, row); grid.add(maxPagesField, 1, row); TextField maxCharsField = boundTextField( editorState.values().maxTextCharacters(), val -> updateValues(editorState.values().withMaxTextCharacters(val))); + applyTooltip(maxCharsField, GuiTooltipTexts.LIMITS_MAX_TEXT_CHARACTERS); grid.add(new Label("Max. Zeichen:"), 2, row); grid.add(maxCharsField, 3, row); row++; @@ -1860,6 +1876,7 @@ public final class GuiConfigurationEditorWorkspace { TextField maxTitleLengthField = boundTextField( editorState.values().maxTitleLength(), val -> updateValues(editorState.values().withMaxTitleLength(val))); + applyTooltip(maxTitleLengthField, GuiTooltipTexts.LIMITS_MAX_TITLE_LENGTH); grid.add(new Label("Max. Titellänge:"), 0, row); grid.add(maxTitleLengthField, 1, row); @@ -1914,9 +1931,11 @@ public final class GuiConfigurationEditorWorkspace { validateButton.setId("validate-button"); validateButton.setOnAction(e -> runValidationAction()); + applyTooltip(validateButton, GuiTooltipTexts.TOOLBAR_VALIDIEREN); technicalTestsButton.setId("technical-tests-button"); technicalTestsButton.setOnAction(e -> runTechnicalTestsAction()); + applyTooltip(technicalTestsButton, GuiTooltipTexts.TOOLBAR_TECHNISCHE_TESTS); HBox buttonRow = new HBox(8, validateButton, technicalTestsButton); buttonRow.setAlignment(Pos.CENTER_LEFT); @@ -2838,6 +2857,22 @@ public final class GuiConfigurationEditorWorkspace { : exception.getMessage(); } + /** + * Setzt einen Tooltip mit einheitlicher Anzeigeverzögerung auf das angegebene Control. + *

+ * Alle Tooltips in dieser Klasse werden über diese Methode gesetzt, damit ein konsistentes + * Erscheinungsbild gewährleistet ist. Darf nur auf dem JavaFX Application Thread aufgerufen werden. + * + * @param control der Button oder ein anderes {@link javafx.scene.control.Control}, das den + * Tooltip erhalten soll; darf nicht {@code null} sein + * @param text der Tooltip-Text; darf nicht leer sein + */ + private static void applyTooltip(javafx.scene.control.Control control, String text) { + Tooltip tooltip = new Tooltip(text); + tooltip.setShowDelay(Duration.millis(300)); + control.setTooltip(tooltip); + } + /** * Speichert den Pfad einer gerade geladenen Konfigurationsdatei. * Der Pfad wird in den Java Preferences gespeichert und beim nächsten Start diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTexts.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTexts.java new file mode 100644 index 0000000..1e15b52 --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTexts.java @@ -0,0 +1,112 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui; + +/** + * Zentrale Konstantenklasse für alle Tooltip-Texte der GUI. + *

+ * Diese Klasse ist die einzige autoritative Quelle für Tooltip-Beschriftungen aller + * interaktiven Elemente in der Desktop-Oberfläche. Alle Tooltip-Strings werden hier + * definiert und von den jeweiligen UI-Klassen referenziert. Streustrings im + * UI-Code sind unzulässig. + *

+ * Tooltip-Texte für Status-Icons werden nicht hier gepflegt – sie stammen + * ausschließlich aus {@link de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.ProcessingStatusPresentation}, + * die die autoritative Quelle für alle statusbezogenen Darstellungsinformationen ist. + *

+ * Alle Texte sind deutschsprachig gemäß Spezifikation. + * Diese Klasse enthält keine JavaFX-Typen und ist nicht instanziierbar. + */ +public final class GuiTooltipTexts { + + // ------------------------------------------------------------------------- + // Toolbar-Buttons + // ------------------------------------------------------------------------- + + /** Tooltip für den Button „Neu". */ + public static final String TOOLBAR_NEU = + "Neue Konfiguration erstellen."; + + /** Tooltip für den Button „Öffnen". */ + public static final String TOOLBAR_OEFFNEN = + "Bestehende Konfigurationsdatei (.properties) öffnen."; + + /** Tooltip für den Button „Speichern". */ + public static final String TOOLBAR_SPEICHERN = + "Aktuelle Konfiguration speichern."; + + /** Tooltip für den Button „Speichern unter". */ + public static final String TOOLBAR_SPEICHERN_UNTER = + "Konfiguration unter neuem Dateipfad speichern."; + + /** Tooltip für den Button „Validieren". */ + public static final String TOOLBAR_VALIDIEREN = + "Aktuelle Eingaben auf Vollständigkeit und Korrektheit prüfen."; + + /** Tooltip für den Button „Technische Tests ausführen". */ + public static final String TOOLBAR_TECHNISCHE_TESTS = + "Dateipfade, Datenbankverbindung und KI-Erreichbarkeit prüfen."; + + // ------------------------------------------------------------------------- + // Konfigurationstab – Pfade + // ------------------------------------------------------------------------- + + /** Tooltip für das Eingabefeld „Quellordner". */ + public static final String PFADE_QUELLORDNER = + "Ordner mit den zu verarbeitenden PDF-Dateien. Inhalt wird nicht verändert."; + + /** Tooltip für das Eingabefeld „Zielordner". */ + public static final String PFADE_ZIELORDNER = + "Ordner für die umbenannten Kopien."; + + /** Tooltip für das Eingabefeld „SQLite-Datei". */ + public static final String PFADE_SQLITE = + "Datenbank für Verarbeitungsergebnisse und Datei-Historie."; + + /** Tooltip für das Eingabefeld „Prompt-Datei". */ + public static final String PFADE_PROMPT = + "Externe Textdatei mit den KI-Anweisungen."; + + // ------------------------------------------------------------------------- + // Konfigurationstab – Provider + // ------------------------------------------------------------------------- + + /** Tooltip für die Provider-ComboBox. */ + public static final String PROVIDER_COMBOBOX = + "Der KI-Dienst, der die Dateinamen generiert."; + + /** Tooltip für das Modell-Eingabefeld (ComboBox oder manuelles TextField). */ + public static final String PROVIDER_MODELL = + "Das konkrete Sprachmodell des gewählten Providers."; + + // ------------------------------------------------------------------------- + // Konfigurationstab – Verarbeitungslimits + // ------------------------------------------------------------------------- + + /** Tooltip für das Eingabefeld „max.text.characters". */ + public static final String LIMITS_MAX_TEXT_CHARACTERS = + "Maximale Zeichenzahl aus dem PDF-Text. Höhere Werte = mehr Kontext, höhere Kosten."; + + /** Tooltip für das Eingabefeld „max.pages". */ + public static final String LIMITS_MAX_PAGES = + "Maximale Seitenzahl, die aus einem PDF gelesen wird."; + + /** Tooltip für das Eingabefeld „max.title.length". */ + public static final String LIMITS_MAX_TITLE_LENGTH = + "Maximale Länge des Dateinamens in Zeichen (ohne Datum und Erweiterung). Gültig: 10–120."; + + // ------------------------------------------------------------------------- + // Verarbeitungslauf-Tab – Dateiname-Editor + // ------------------------------------------------------------------------- + + /** Tooltip für den Button „Dateiname übernehmen". */ + public static final String DATEINAME_UEBERNEHMEN = + "Benennt die Zieldatei um und aktualisiert die Datenbank. Nicht rückgängig zu machen."; + + /** Tooltip für den Button „Zurücksetzen auf KI-Vorschlag". */ + public static final String DATEINAME_ZURUECKSETZEN = + "Stellt den KI-generierten Namen wieder her, ohne zu speichern."; + + /** Nicht instanziierbar – reine Konstantenklasse. */ + private GuiTooltipTexts() { + throw new UnsupportedOperationException("Nicht instanziierbar"); + } +} diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/FileNameEditorPane.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/FileNameEditorPane.java index 03f750e..d8b1665 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/FileNameEditorPane.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/FileNameEditorPane.java @@ -6,17 +6,20 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiTooltipTexts; import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; import javafx.scene.input.KeyCode; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.util.Duration; /** * Detailbereich-Komponente für die Bearbeitung des Zieldateinamens einer selektierten @@ -86,9 +89,15 @@ public final class FileNameEditorPane { saveButton.setId("filename-editor-save-button"); saveButton.setOnAction(e -> fireSaveRequest()); + Tooltip saveTooltip = new Tooltip(GuiTooltipTexts.DATEINAME_UEBERNEHMEN); + saveTooltip.setShowDelay(Duration.millis(300)); + saveButton.setTooltip(saveTooltip); resetButton.setId("filename-editor-reset-button"); resetButton.setOnAction(e -> resetToAiProposal()); + Tooltip resetTooltip = new Tooltip(GuiTooltipTexts.DATEINAME_ZURUECKSETZEN); + resetTooltip.setShowDelay(Duration.millis(300)); + resetButton.setTooltip(resetTooltip); HBox buttonRow = new HBox(8, saveButton, resetButton); buttonRow.setAlignment(Pos.CENTER_LEFT); diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/editor/GuiModelFieldContainer.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/editor/GuiModelFieldContainer.java index 3b1e7d0..bdee0e9 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/editor/GuiModelFieldContainer.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/editor/GuiModelFieldContainer.java @@ -8,7 +8,9 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.ComboBox; import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; import javafx.scene.layout.StackPane; +import javafx.util.Duration; /** * A container that switches between a non-editable {@link ComboBox} and a manual {@link TextField} @@ -169,6 +171,26 @@ public final class GuiModelFieldContainer extends StackPane { } } + /** + * Setzt einen Tooltip mit einheitlicher Anzeigeverzögerung auf beide internen Controls + * (ComboBox und TextField). Damit erscheint der Tooltip unabhängig davon, welches der + * beiden Controls gerade sichtbar ist. + *

+ * Darf nur auf dem JavaFX Application Thread aufgerufen werden. + * + * @param tooltipText der anzuzeigende Tooltip-Text; darf nicht leer sein + */ + public void applyTooltip(String tooltipText) { + Objects.requireNonNull(tooltipText, "tooltipText darf nicht null sein"); + Tooltip comboTooltip = new Tooltip(tooltipText); + comboTooltip.setShowDelay(Duration.millis(300)); + comboBox.setTooltip(comboTooltip); + + Tooltip textTooltip = new Tooltip(tooltipText); + textTooltip.setShowDelay(Duration.millis(300)); + textField.setTooltip(textTooltip); + } + /** * Returns the JavaFX node that represents this container and can be added to the scene graph. * diff --git a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTextsTest.java b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTextsTest.java new file mode 100644 index 0000000..f53c3d4 --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiTooltipTextsTest.java @@ -0,0 +1,205 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui; + +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.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * Unit-Tests für {@link GuiTooltipTexts}. + *

+ * Prüft, dass alle öffentlichen Tooltip-Konstanten vorhanden sind, nicht leer sind + * und den exakten Texten gemäß Spezifikation entsprechen. + */ +class GuiTooltipTextsTest { + + // ------------------------------------------------------------------------- + // Vollständigkeit und Nicht-Leerheit aller Konstanten + // ------------------------------------------------------------------------- + + @Test + void alleKonstantenSindNichtNullUndNichtLeer() { + List fehler = new ArrayList<>(); + for (Field field : GuiTooltipTexts.class.getDeclaredFields()) { + if (!Modifier.isPublic(field.getModifiers()) + || !Modifier.isStatic(field.getModifiers()) + || !Modifier.isFinal(field.getModifiers())) { + continue; + } + try { + Object value = field.get(null); + if (value == null) { + fehler.add(field.getName() + " ist null"); + } else if (value instanceof String s && s.isBlank()) { + fehler.add(field.getName() + " ist leer"); + } + } catch (IllegalAccessException e) { + fehler.add(field.getName() + " nicht zugreifbar: " + e.getMessage()); + } + } + if (!fehler.isEmpty()) { + org.junit.jupiter.api.Assertions.fail( + "Fehlerhafte Tooltip-Konstanten: " + String.join(", ", fehler)); + } + } + + // ------------------------------------------------------------------------- + // Toolbar-Tooltips – exakter Text gemäß Spezifikation + // ------------------------------------------------------------------------- + + @Test + void toolbar_neu_entsprichtSpezifikation() { + assertNotNull(GuiTooltipTexts.TOOLBAR_NEU); + assertFalse(GuiTooltipTexts.TOOLBAR_NEU.isBlank()); + org.junit.jupiter.api.Assertions.assertEquals( + "Neue Konfiguration erstellen.", + GuiTooltipTexts.TOOLBAR_NEU); + } + + @Test + void toolbar_oeffnen_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Bestehende Konfigurationsdatei (.properties) öffnen.", + GuiTooltipTexts.TOOLBAR_OEFFNEN); + } + + @Test + void toolbar_speichern_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Aktuelle Konfiguration speichern.", + GuiTooltipTexts.TOOLBAR_SPEICHERN); + } + + @Test + void toolbar_speichernUnter_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Konfiguration unter neuem Dateipfad speichern.", + GuiTooltipTexts.TOOLBAR_SPEICHERN_UNTER); + } + + @Test + void toolbar_validieren_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Aktuelle Eingaben auf Vollständigkeit und Korrektheit prüfen.", + GuiTooltipTexts.TOOLBAR_VALIDIEREN); + } + + @Test + void toolbar_technischeTests_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Dateipfade, Datenbankverbindung und KI-Erreichbarkeit prüfen.", + GuiTooltipTexts.TOOLBAR_TECHNISCHE_TESTS); + } + + // ------------------------------------------------------------------------- + // Pfade-Tooltips – exakter Text gemäß Spezifikation + // ------------------------------------------------------------------------- + + @Test + void pfade_quellordner_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Ordner mit den zu verarbeitenden PDF-Dateien. Inhalt wird nicht verändert.", + GuiTooltipTexts.PFADE_QUELLORDNER); + } + + @Test + void pfade_zielordner_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Ordner für die umbenannten Kopien.", + GuiTooltipTexts.PFADE_ZIELORDNER); + } + + @Test + void pfade_sqlite_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Datenbank für Verarbeitungsergebnisse und Datei-Historie.", + GuiTooltipTexts.PFADE_SQLITE); + } + + @Test + void pfade_prompt_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Externe Textdatei mit den KI-Anweisungen.", + GuiTooltipTexts.PFADE_PROMPT); + } + + // ------------------------------------------------------------------------- + // Provider-Tooltips – exakter Text gemäß Spezifikation + // ------------------------------------------------------------------------- + + @Test + void provider_combobox_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Der KI-Dienst, der die Dateinamen generiert.", + GuiTooltipTexts.PROVIDER_COMBOBOX); + } + + @Test + void provider_modell_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Das konkrete Sprachmodell des gewählten Providers.", + GuiTooltipTexts.PROVIDER_MODELL); + } + + // ------------------------------------------------------------------------- + // Verarbeitungslimits-Tooltips – exakter Text gemäß Spezifikation + // ------------------------------------------------------------------------- + + @Test + void limits_maxTextCharacters_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Maximale Zeichenzahl aus dem PDF-Text. Höhere Werte = mehr Kontext, höhere Kosten.", + GuiTooltipTexts.LIMITS_MAX_TEXT_CHARACTERS); + } + + @Test + void limits_maxPages_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Maximale Seitenzahl, die aus einem PDF gelesen wird.", + GuiTooltipTexts.LIMITS_MAX_PAGES); + } + + @Test + void limits_maxTitleLength_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Maximale Länge des Dateinamens in Zeichen (ohne Datum und Erweiterung). Gültig: 10–120.", + GuiTooltipTexts.LIMITS_MAX_TITLE_LENGTH); + } + + // ------------------------------------------------------------------------- + // Verarbeitungslauf-Tab-Tooltips – exakter Text gemäß Spezifikation + // ------------------------------------------------------------------------- + + @Test + void dateiname_uebernehmen_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Benennt die Zieldatei um und aktualisiert die Datenbank. Nicht rückgängig zu machen.", + GuiTooltipTexts.DATEINAME_UEBERNEHMEN); + } + + @Test + void dateiname_zuruecksetzen_entsprichtSpezifikation() { + org.junit.jupiter.api.Assertions.assertEquals( + "Stellt den KI-generierten Namen wieder her, ohne zu speichern.", + GuiTooltipTexts.DATEINAME_ZURUECKSETZEN); + } + + // ------------------------------------------------------------------------- + // Nicht instanziierbar + // ------------------------------------------------------------------------- + + @Test + void konstruktorWirftException() throws Exception { + Constructor ctor = GuiTooltipTexts.class.getDeclaredConstructor(); + ctor.setAccessible(true); + assertThrows(java.lang.reflect.InvocationTargetException.class, ctor::newInstance, + "Der private Konstruktor muss UnsupportedOperationException werfen"); + } +}