M13 vollständig abgeschlossen: V2.0-Freigabe (AP-001 bis AP-009)
- AP-001: Betriebs- und Startdokumentation für GUI und headless konsolidiert (betrieb.md, README.md) - AP-002: Endbenutzer-Bedienanleitung gui-bedienanleitung.md angelegt (deskriptiv, 13 Kapitel, deutsch, Windows-Hinweise) - AP-003: Konfigurationsbeispiele docs/examples/application.properties und docs/examples/prompt.txt konsolidiert, konsistent mit Standardvorlage - AP-004: Regressionstests für headless Abwärtskompatibilität (JAR-Smoke-IT mit --config-Varianten und JavaFX-Freiheit) - AP-005: GUI-Smoke-Tests für V2.0-Kernumfang vervollständigt (Startup-Notice-Sichtbarkeit im Header) - AP-006: Build- und Packaging-Dokumentation im Abschnitt "Build und Packaging" in betrieb.md, README-Artefaktnamen korrigiert - AP-007: Integrierte Gesamtprüfung durchgeführt, V2.0-Abschnitt in befundliste.md — keine Release-Blocker, zwei nicht blockierende Restpunkte (R1 ByteBuddy-Warning, R2 fehlender visueller GUI-Render-Test) - AP-008: entfiel (keine Release-Blocker zu beheben) - AP-009: Finale Gesamtprüfung, Freigabedokument docs/freigabe-v2_0.md mit Git-HEAD, Build-/Test-Ergebnissen, Freigabeaussage. Ein während der Stichprobe entdeckter Doku-Defekt (R3: API-Key-Legacy-Variable) wurde unmittelbar in gui-bedienanleitung.md korrigiert. V2.0 ist freigabefähig. 1.403 Tests grün, 0 Failures, 0 Errors. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+7
@@ -108,6 +108,13 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
private final Label configurationPathValueLabel = new Label();
|
||||
/** Package-private to allow visibility assertions in smoke tests. */
|
||||
final Label dirtyMarkerLabel = new Label("geändert");
|
||||
/**
|
||||
* Package-private to allow startup-notice visibility assertions in smoke tests.
|
||||
* Returns the status label used to display startup notices and status messages in the header.
|
||||
*/
|
||||
Label statusNoticeLabel() {
|
||||
return statusLabel;
|
||||
}
|
||||
private final Label welcomeTitleLabel = new Label("Willkommen");
|
||||
private final Label welcomeTextLabel = new Label(WELCOME_TEXT);
|
||||
/** Package-private to allow node lookups in smoke tests. */
|
||||
|
||||
+90
@@ -2,6 +2,7 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
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.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -323,6 +324,95 @@ class GuiEditorIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GUI startup with a non-existent --config path: startup notice rendered in header
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Verifies that when the workspace is constructed with a startup notice (as Bootstrap does
|
||||
* when {@code --config} points to a non-existent file), the notice text is rendered in the
|
||||
* visible header status label.
|
||||
* <p>
|
||||
* This complements {@link #guiStartup_withNonExistentConfigPath_usesBlankStateAndCarriesStartupNotice}
|
||||
* which only verifies the blank editor state. This test verifies the user-visible side: the
|
||||
* notice must be rendered so the user can read it.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void guiStartup_withNonExistentConfigPath_noticeIsRenderedInHeaderStatusLabel()
|
||||
throws Exception {
|
||||
String notice = "Konfigurationsdatei nicht gefunden: /no/such/file.properties\n"
|
||||
+ "Die GUI startet ohne Konfigurationsdatei.";
|
||||
GuiConfigurationEditorState blankState = GuiConfigurationEditorStateFactory.createBlankStartState();
|
||||
GuiConfigurationFileWriter noOpWriter = (values, path) -> GuiConfigurationSaveResult.saved(path);
|
||||
GuiStartupContext context = new GuiStartupContext(
|
||||
blankState,
|
||||
Optional.of(notice),
|
||||
configFilePath -> GuiConfigurationEditorStateFactory.createBlankStartState(),
|
||||
noOpWriter,
|
||||
req -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req.providerIdentifier(), "kein Port im Test"),
|
||||
(family, propertyValue) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent(),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService(
|
||||
req2 -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req2.providerIdentifier(), "kein Port im Test"),
|
||||
(fam2, pv2) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.PathCheckPort() {
|
||||
@Override public boolean isDirectoryReadable(String p) { return false; }
|
||||
@Override public boolean isDirectoryWritableOrCreatable(String p) { return false; }
|
||||
@Override public boolean isFileReadable(String p) { return false; }
|
||||
@Override public boolean isSqlitePathUsable(String p) { return false; }
|
||||
},
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.TechnicalTestOrchestrator(
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator(),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.PathCheckPort() {
|
||||
@Override public boolean isDirectoryReadable(String p) { return false; }
|
||||
@Override public boolean isDirectoryWritableOrCreatable(String p) { return false; }
|
||||
@Override public boolean isFileReadable(String p) { return false; }
|
||||
@Override public boolean isSqlitePathUsable(String p) { return false; }
|
||||
},
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService(
|
||||
req99 -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req99.providerIdentifier(), "kein Port im Test"),
|
||||
(fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent())),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() {
|
||||
@Override public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome createDirectory(de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionSuggestion.CreateDirectory s) { return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome.NotAttempted(s, "kein Port im Test"); }
|
||||
@Override public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome createPromptFile(de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionSuggestion.CreatePromptFile s) { return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome.NotAttempted(s, "kein Port im Test"); }
|
||||
@Override public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome prepareSqlitePath(de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionSuggestion.PrepareSqlitePath s) { return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome.NotAttempted(s, "kein Port im Test"); }
|
||||
}));
|
||||
|
||||
AtomicReference<Throwable> error = new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
GuiConfigurationEditorWorkspace workspace = new GuiConfigurationEditorWorkspace(context);
|
||||
|
||||
// The startup notice must be rendered in the visible header status label.
|
||||
javafx.scene.control.Label noticeLabel = workspace.statusNoticeLabel();
|
||||
assertNotNull(noticeLabel, "Header status label must not be null");
|
||||
assertTrue(noticeLabel.isVisible(),
|
||||
"Header status label must be visible when a startup notice is present");
|
||||
assertTrue(noticeLabel.isManaged(),
|
||||
"Header status label must be managed when a startup notice is present");
|
||||
assertFalse(noticeLabel.getText().isBlank(),
|
||||
"Header status label text must not be blank when startup notice is present");
|
||||
assertTrue(noticeLabel.getText().contains("Konfigurationsdatei nicht gefunden"),
|
||||
"Header status label must contain the notice text; got: " + noticeLabel.getText());
|
||||
|
||||
} catch (Throwable t) {
|
||||
error.set(t);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS),
|
||||
"FX task must complete within timeout");
|
||||
if (error.get() != null) {
|
||||
throw new AssertionError("FX thread threw an exception", error.get());
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// --config path resolution: static helper (no FX thread needed)
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user