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 @@

Lizenz - Version + Version DCAT-AP 3.0 PHP >= 8.1 WordPress 6.4+ 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.' ); + } }