diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f59ce6..972aaeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.ht --- +## [2.41.1] — 2026-08-27 + +Ein Nachtrag zum Usability-Test: der Startwert der Qualitätsprüfung. + +### 🐛 Fixed +- **„Neuen Datensatz anlegen" startete bei 7 %, nicht bei 0 %.** Zwei Ursachen, beide + dieselbe Sorte Fehler — der Wizard rechnete sich an, was er selbst vorgibt. WordPress legt + beim Öffnen des Formulars einen Auto-Entwurf an und feuert dabei `save_post`: Das + Änderungsdatum wurde gestempelt (5 Punkte), obwohl niemand etwas gespeichert hatte. Und + Carbon Fields liefert für ungespeicherte Felder ihren Vorgabewert zurück — die Zugriffsrechte + stehen auf „öffentlich", das zählte als Angabe plus Vokabulartreffer (10 + 5 Punkte). + Zusammen 20 von 295 bewertbaren Punkten. Der Bericht wertet auf einem Auto-Entwurf jetzt + nichts als erfüllt, und `set_modified_date()` überspringt ihn. Der Nenner bleibt dabei + stehen: Die Metriken sind prüfbar, sie sind nur noch nicht erfüllt — 0 von 0 Punkten ergäbe + wieder eine überraschende Zahl. Zwei Unit-Tests und ein E2E-Test halten die 0 fest. + +--- + ## [2.41.0] — 2026-08-27 Fünf Punkte aus dem Usability-Test. Hilfetexte haben jetzt je einen Ort, die Definition zeigt diff --git a/CLAUDE.md b/CLAUDE.md index b878b8f..62f5cb3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -765,7 +765,7 @@ Update **both** locations: - MINOR: New features (backward-compatible) - PATCH: Bug fixes only -Current: **v2.39.1** +Current: **v2.41.1** --- diff --git a/README.md b/README.md index d537bba..ce4e058 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
-
+
diff --git a/includes/class-fields.php b/includes/class-fields.php
index 5fcd4e2..cf25b8b 100644
--- a/includes/class-fields.php
+++ b/includes/class-fields.php
@@ -695,6 +695,15 @@ public static function set_modified_date( int $post_id, \WP_Post $post ): void {
return;
}
+ // Auto-Entwürfe überspringen: WordPress legt sie beim Öffnen von „Neuer
+ // Datensatz" an und feuert dabei save_post. Ein dct:modified zu schreiben,
+ // bevor überhaupt jemand etwas eingetragen hat, datiert den Datensatz auf
+ // das Aufrufen des Formulars — und ließ die Qualitätsprüfung mit Punkten
+ // starten, die niemand verdient hatte.
+ if ( 'auto-draft' === $post->post_status ) {
+ return;
+ }
+
remove_action( 'save_post', array( self::class, 'set_modified_date' ), 20 );
update_post_meta( $post_id, '_odw_modified', current_time( 'Y-m-d' ) );
add_action( 'save_post', array( self::class, 'set_modified_date' ), 20, 2 );
diff --git a/includes/class-quality.php b/includes/class-quality.php
index 01f04a1..6c0d94f 100644
--- a/includes/class-quality.php
+++ b/includes/class-quality.php
@@ -202,16 +202,28 @@ public static function calculate( int $post_id ): array {
private static function evaluate_metric( array $metric, \WP_Post $post ): ?bool {
$type = (string) ( $metric['type'] ?? '' );
+ // Ein Auto-Entwurf ist nichts, was jemand gespeichert hat: WordPress legt
+ // ihn beim Öffnen von „Neuer Datensatz" selbst an. Carbon Fields liefert
+ // für ungespeicherte Felder ihren Vorgabewert zurück — bei den
+ // Zugriffsrechten etwa „öffentlich". Ohne diese Abfrage zählte der Bericht
+ // also, was das Formular vorschlägt, statt was jemand eingetragen hat, und
+ // ein frisch geöffnetes Formular stünde nicht bei 0 %.
+ //
+ // Nicht erfüllt, nicht „nicht bewertbar": Die Metrik ist prüfbar, sie ist
+ // nur (noch) nicht erfüllt. Sonst schrumpfte der Nenner und 0 von 0
+ // Punkten ergäbe wieder eine Zahl, die niemand erwartet.
+ $ungespeichert = 'auto-draft' === $post->post_status;
+
if ( 'present' === $type ) {
- return self::check_metric( (string) $metric['check'], $post );
+ return $ungespeichert ? false : self::check_metric( (string) $metric['check'], $post );
}
if ( 'vocab' === $type ) {
- return self::check_vocab_metric( (string) $metric['check'], $post );
+ return $ungespeichert ? false : self::check_vocab_metric( (string) $metric['check'], $post );
}
if ( 'reachable' === $type && self::url_checks_enabled() ) {
- return self::check_reachable_metric( (string) $metric['check'], $post );
+ return $ungespeichert ? false : self::check_reachable_metric( (string) $metric['check'], $post );
}
// SHACL (und Erreichbarkeit bei deaktivierter Einstellung) folgt in Phase 3+.
@@ -779,8 +791,8 @@ public static function render_meta_box( \WP_Post $post ): void {
{
await expect(page.locator(cfField('odw_cessda_topic'))).toHaveValue(/^https?:\/\//);
});
+ test('ein frisch geöffnetes Formular startet bei 0 %', async ({ page }) => {
+ await page.goto('/wp-admin/post-new.php?post_type=odw_dataset');
+
+ // WordPress legt beim Öffnen einen Auto-Entwurf an und feuert dabei
+ // save_post. Vorher zählte der Bericht dessen Vorgabewerte mit — die
+ // Zugriffsrechte stehen auf „öffentlich", das Änderungsdatum wurde
+ // gestempelt — und das leere Formular startete bei 7 %.
+ const box = page.locator('#odw-quality-report');
+ await expect(box.locator('.odw-quality-percent')).toHaveText('0 %');
+ });
+
test('wechselt auf den zweiten Reiter', async ({ page }) => {
await page.goto('/wp-admin/post-new.php?post_type=odw_dataset');
diff --git a/tests/test-quality.php b/tests/test-quality.php
index ef342d4..42a0124 100644
--- a/tests/test-quality.php
+++ b/tests/test-quality.php
@@ -9,6 +9,41 @@
use PHPUnit\Framework\TestCase;
+if ( ! class_exists( 'WP_Post' ) ) {
+ /**
+ * Minimaler WP_Post-Ersatz.
+ *
+ * ODW_Quality::calculate() und evaluate_metric() sind auf \WP_Post typisiert,
+ * WP_Mock bringt die Klasse aber nicht mit. Mehr als diese drei Felder liest
+ * der Qualitätsbericht nicht.
+ *
+ * @package OpenDataWizard
+ */
+ class WP_Post {
+
+ /**
+ * Post ID.
+ *
+ * @var int
+ */
+ public $ID = 0;
+
+ /**
+ * Post type.
+ *
+ * @var string
+ */
+ public $post_type = 'odw_dataset';
+
+ /**
+ * Post status.
+ *
+ * @var string
+ */
+ public $post_status = 'draft';
+ }
+}
+
/**
* Unit tests for ODW_Quality.
*
@@ -550,4 +585,77 @@ function ( $id, $key ) {
$this->assertSame( 'http://dcat-ap.de/def/licenses/cc-by/4.0', $rows[1]['license'] );
}
+
+ // -------------------------------------------------------------------------
+ // Startwert eines frisch geöffneten Formulars
+ // -------------------------------------------------------------------------
+
+ /**
+ * Baut ein Post-Objekt für die Bewertung.
+ *
+ * @param string $status Post-Status.
+ * @return \WP_Post
+ */
+ private function post_with_status( string $status ): \WP_Post {
+ $post = new \WP_Post();
+ $post->ID = 42;
+ $post->post_type = 'odw_dataset';
+ $post->post_status = $status;
+
+ return $post;
+ }
+
+ /**
+ * Auf einem Auto-Entwurf zählt kein Wert — auch keiner, der dasteht.
+ *
+ * WordPress legt den Auto-Entwurf beim Öffnen von „Neuer Datensatz" selbst
+ * an. Carbon Fields liefert für ungespeicherte Felder ihren Vorgabewert
+ * zurück; ohne die Abfrage zählte der Bericht also, was das Formular
+ * vorschlägt. Derselbe Meta-Wert, einmal je Status: Nur der Entwurf zählt.
+ */
+ public function test_evaluate_metric_counts_nothing_on_auto_draft(): void {
+ $this->load_class();
+
+ \WP_Mock::userFunction( 'get_post_meta' )->andReturn( '2026-08-27' );
+
+ $metric = array(
+ 'key' => 'modified',
+ 'type' => 'present',
+ 'check' => 'modified',
+ );
+
+ $this->assertTrue(
+ $this->call_private( 'evaluate_metric', $metric, $this->post_with_status( 'draft' ) ),
+ 'Ein gespeicherter Entwurf mit Änderungsdatum muss die Metrik erfüllen.'
+ );
+
+ $this->assertFalse(
+ $this->call_private( 'evaluate_metric', $metric, $this->post_with_status( 'auto-draft' ) ),
+ 'Auf dem Auto-Entwurf darf derselbe Wert nicht zählen.'
+ );
+ }
+
+ /**
+ * „Neuen Datensatz anlegen" startet bei 0 %, nicht bei 7 %.
+ *
+ * Die Mocks liefern absichtlich für jedes Feld einen gefüllten Wert: Der Test
+ * hält damit nicht bloß fest, dass ein leeres Formular 0 ergibt, sondern dass
+ * der Auto-Entwurf gar nichts anrechnet. Der Nenner muss stehen bleiben —
+ * 0 von 0 Punkten ergäbe sonst wieder eine überraschende Zahl.
+ */
+ public function test_calculate_scores_a_fresh_form_at_zero(): void {
+ $this->load_class();
+
+ \WP_Mock::userFunction( 'get_post' )->andReturn( $this->post_with_status( 'auto-draft' ) );
+ \WP_Mock::userFunction( 'carbon_get_post_meta' )->andReturn( 'irgendein Wert' );
+ \WP_Mock::userFunction( 'get_post_meta' )->andReturn( '2026-08-27' );
+ \WP_Mock::userFunction( 'current_time' )->andReturn( '2026-08-27 10:00:00' );
+ \WP_Mock::userFunction( '__' )->andReturnArg( 0 );
+
+ $result = ODW_Quality::calculate( 42 );
+
+ $this->assertSame( 0, $result['achieved'] );
+ $this->assertSame( 0, $result['score'] );
+ $this->assertGreaterThan( 0, $result['assessable'], 'Die Metriken bleiben bewertbar, sie sind nur nicht erfüllt.' );
+ }
}