justReturn(true); Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://k.de/wp-admin/' . $path); Functions\when('add_query_arg')->alias( static fn (array $args, string $url): string => $url . '?' . http_build_query($args) ); Functions\when('sanitize_key')->alias(static fn (string $key): string => strtolower($key)); Functions\when('wp_unslash')->returnArg(); } protected function tearDown(): void { unset($_GET['tab']); parent::tearDown(); } private static function extras( string $version = '5.3.0', ?FeatureOverview $overview = null, bool $report = false ): DiviAdminExtras { $reports = Mockery::mock(CheckReportStore::class); $reports->allows('latest')->andReturn($report ? new CheckReport('r1', '2026-10-01T08:00:00+00:00', 1, '5.3.0', '0.7.0', 'key', []) : null); return new DiviAdminExtras($overview, static fn (): string => $version, $reports); } private static function mixedStatus(): ReleaseStatus { $matrix = MatrixFixtures::released('auto', [ new FeatureStatus( DiviFeature::PageWrite, ReleaseState::Incompatible, ['Seiten: Signatur von et_pb_x() '], null ), new FeatureStatus(DiviFeature::Catalog, ReleaseState::Untested, [], null), ]); return ReleaseStatus::of('5.3.0', $matrix); } private static function extraHtml(ReleaseStatus $status, ?DiviAdminExtras $extras = null, string $id = 'divi'): string { ReleaseGate::useResolver(static fn (): ReleaseStatus => $status); ob_start(); ($extras ?? self::extras())->renderExtras($id); return (string) ob_get_clean(); } private static function introHtml(ReleaseStatus $status, ?DiviAdminExtras $extras = null, string $id = 'divi'): string { ReleaseGate::useResolver(static fn (): ReleaseStatus => $status); ob_start(); ($extras ?? self::extras())->renderIntro($id); return (string) ob_get_clean(); } private static function explanationOf(ReleaseStatus $status, string $id = 'divi', string $generic = 'generisch'): string { ReleaseGate::useResolver(static fn (): ReleaseStatus => $status); return self::extras()->explanation($generic, $id, $status->state); } private static function noticeHtml(ReleaseStatus $status, string $version = '5.3.0'): string { ReleaseGate::useResolver(static fn (): ReleaseStatus => $status); ob_start(); self::extras($version)->renderNotice(); return (string) ob_get_clean(); } public function testItHooksIntoTheCompatibilityTabAndTheAdminNotices(): void { $extras = self::extras(); Actions\expectAdded('admin_notices')->once()->with([$extras, 'renderNotice']); Actions\expectAdded(CompatibilityTab::LOAD_ACTION)->once()->with([$extras, 'removeOrphans']); Actions\expectAdded(CompatibilityTab::INTRO_ACTION)->once()->with([$extras, 'renderIntro']); Actions\expectAdded(CompatibilityTab::EXTRA_ACTION)->once()->with([$extras, 'renderExtras']); Filters\expectAdded(CompatibilityTab::EXPLANATION_FILTER)->once()->with([$extras, 'explanation'], 10, 3); Filters\expectAdded(CompatibilityTab::COLUMNS_FILTER)->once()->with([$extras, 'columns'], 10, 2); $extras->register(); } public function testTheNoticeNamesTheUnusableFeaturesAndLinksToTheCompatibilityTab(): void { $html = self::noticeHtml(self::mixedStatus()); self::assertStringContainsString('class="notice notice-warning"', $html); self::assertStringContainsString('Mit Divi 5.3.0 sind diese Fähigkeiten nicht nutzbar', $html); self::assertStringContainsString('Seiten schreiben, Modulkatalog', $html); self::assertStringContainsString('alle anderen laufen weiter', $html); self::assertStringContainsString('page=lit-mcp&tab=compatibility', $html); self::assertStringContainsString('Reiter „Compatibility“', $html); } public function testNoNoticeWhileEveryFeatureIsUsable(): void { self::assertSame('', self::noticeHtml(ReleaseMatrices::released('5.3.0'))); } public function testNoNoticeForUsersWithoutManageOptions(): void { Functions\when('current_user_can')->justReturn(false); self::assertSame('', self::noticeHtml(self::mixedStatus())); } public function testNoNoticeWithoutDivi(): void { self::assertSame('', self::noticeHtml(self::mixedStatus(), '')); } public function testNoNoticeOnTheCompatibilityTabItself(): void { Functions\when('get_current_screen')->justReturn((object) ['id' => 'settings_page_lit-mcp']); $_GET['tab'] = 'compatibility'; self::assertSame('', self::noticeHtml(self::mixedStatus())); } public function testTheNoticeStillShowsOnOtherTabsOfThePluginPage(): void { Functions\when('get_current_screen')->justReturn((object) ['id' => 'settings_page_lit-mcp']); $_GET['tab'] = 'tools'; self::assertStringContainsString('nicht nutzbar', self::noticeHtml(self::mixedStatus())); } /** The orphans go in the load action of the tab, never while rendering. */ public function testTheLoadActionRemovesOrphansAndTheExtrasReportThem(): void { Functions\expect('get_posts')->once()->andReturn([ new WP_Post(['ID' => 51, 'post_title' => WriteProbe::TITLE . ' 2026-10-18 08:00']), ]); Functions\expect('wp_delete_post')->once()->with(51, true)->andReturn(new WP_Post(['ID' => 51])); $extras = self::extras(); $extras->removeOrphans('divi'); $html = self::introHtml(ReleaseMatrices::released('5.3.0'), $extras); self::assertStringContainsString('Liegengebliebene Prüfseiten gelöscht: 51', $html); self::assertStringNotContainsString('Liegengebliebene', self::extraHtml(ReleaseMatrices::released('5.3.0'), $extras)); } public function testRenderingAloneDeletesNothing(): void { Functions\expect('get_posts')->never(); Functions\expect('wp_delete_post')->never(); $html = self::introHtml(ReleaseMatrices::released('5.3.0')); self::assertStringNotContainsString('Liegengebliebene Prüfseiten', $html); } public function testTheLoadActionDeletesNothingForOtherProfilesWithoutTheRightOrWithoutDivi(): void { Functions\expect('get_posts')->never(); self::extras()->removeOrphans('pods'); Functions\when('current_user_can')->justReturn(false); self::extras()->removeOrphans('divi'); Functions\when('current_user_can')->justReturn(true); self::extras('')->removeOrphans('divi'); } public function testTheIntroAndExtrasOfOtherProfilesAreEmpty(): void { self::assertSame('', self::extraHtml(ReleaseMatrices::released('5.3.0'), null, 'pods')); self::assertSame('', self::introHtml(self::mixedStatus(), null, 'pods')); } public function testTheExtrasExplainTheAutomationTheFilterAndTheVisualBuilderWithAReport(): void { $html = self::extraHtml(ReleaseMatrices::released('5.3.0'), self::extras('5.3.0', null, true)); self::assertStringContainsString('gibt LIT MCP jede Fähigkeit einzeln frei', $html); self::assertStringContainsString('lit_mcp_divi_auto_release', $html); self::assertStringContainsString('als ungetestet', $html); self::assertStringContainsString('Aufrufe', $html); self::assertStringNotContainsString('Verhalten geprüft', $html); self::assertStringContainsString('Visual Builder', $html); } public function testTheVisualBuilderHintNeedsAReport(): void { $html = self::extraHtml(ReleaseMatrices::released('5.3.0')); self::assertStringNotContainsString('Visual Builder', $html); self::assertStringContainsString('gibt LIT MCP jede Fähigkeit einzeln frei', $html); } public function testTheIntroSaysWhatTheCheckDoes(): void { $html = self::introHtml(ReleaseMatrices::released('5.3.0')); self::assertStringContainsString('legt eine private Testseite an, prüft Lesen und Schreiben und löscht sie wieder', $html); } public function testAReleasedVersionNeedsNoNextStep(): void { self::assertStringNotContainsString('notice notice-', self::introHtml(ReleaseMatrices::released('5.3.0'))); } public function testAnIncompatibleVersionNamesThePluginUpdateAsTheNextStep(): void { $html = self::introHtml(self::mixedStatus()); self::assertStringContainsString('notice notice-error inline', $html); self::assertStringContainsString('Plugin-Update', $html); self::assertStringNotContainsString('lesen nur', $html); } public function testAnUntestedVersionNamesTheCheckAsTheNextStep(): void { $html = self::introHtml(ReleaseMatrices::uniform(ReleaseState::Untested)); self::assertStringContainsString('notice notice-warning inline', $html); self::assertStringContainsString('Divi 5.3.0 ist noch nicht geprüft. Starte die Prüfung', $html); } public function testTheExplanationNamesTheSourceOfAReleasedVersion(): void { Functions\when('get_userdata')->justReturn((object) ['display_name' => 'Jan ']); Functions\when('get_option')->justReturn('Y-m-d'); Functions\when('date_i18n')->alias(static fn (string $format, int $time): string => gmdate($format, $time)); $code = self::explanationOf(ReleaseStatus::of('5.3.0', MatrixFixtures::released('code'))); $auto = self::explanationOf(ReleaseStatus::of('5.3.0', MatrixFixtures::released('auto'))); $database = self::explanationOf(ReleaseStatus::of( '5.3.0', MatrixFixtures::released('database'), ['user_id' => 7, 'released_at' => '2026-10-01T08:00:00+00:00'] )); self::assertSame('Diese Version ist im Plugin als getestet hinterlegt.', $code); self::assertStringStartsWith('Automatisch freigegeben: Alle Divi-Stellen', $auto); self::assertSame('Auf dieser Website freigegeben am 2026-10-01 von Jan .', $database); } public function testTheExplanationOfADatabaseReleaseWithoutAKnownUserNamesAQuestionMark(): void { Functions\when('get_userdata')->justReturn(false); Functions\when('get_option')->justReturn('Y-m-d'); Functions\when('date_i18n')->alias(static fn (string $format, int $time): string => gmdate($format, $time)); $text = self::explanationOf(ReleaseStatus::of('5.3.0', MatrixFixtures::released('database'), ['user_id' => 7])); self::assertStringEndsWith('von ?.', $text); } public function testTheExplanationHasOwnTextsForUntestedAndIncompatible(): void { $untested = self::explanationOf(ReleaseMatrices::uniform(ReleaseState::Untested)); $incompatible = self::explanationOf(self::mixedStatus()); self::assertStringContainsString('Diese Version ist nicht geprüft. Die Tools der ungetesteten Fähigkeiten', $untested); self::assertStringContainsString('bis ein Plugin-Update von LIT MCP eingespielt ist', $incompatible); } public function testTheExplanationOfOtherProfilesStaysGeneric(): void { self::assertSame('generisch', self::explanationOf(self::mixedStatus(), 'pods')); } /** @return list */ private static function columns(DiviAdminExtras $extras, string $id = 'divi'): array { return $extras->columns([], $id); } /** @return list> The cells of one capability across the three columns. */ private static function cellsOf(DiviFeature $feature, array $columns, ReleaseStatus $status): array { return array_map(static fn (array $column): array => ($column['cell'])($feature, $status->matrix), $columns); } public function testTheCapabilityTableGetsSourceProblemsAndAffectedTools(): void { $overview = new FeatureOverview(static fn (): array => [ new FeatureFakeTool('lit/divi-insert', true, [DiviFeature::PageRead, DiviFeature::PageWrite]), new FeatureFakeTool('lit/divi-presets-assign', false, [DiviFeature::PresetWrite]), ]); $columns = self::columns(self::extras('5.3.0', $overview)); $status = self::mixedStatus(); self::assertSame(['Quelle', 'Probleme', 'Betroffene Tools'], array_column($columns, 'heading')); self::assertTrue($columns[2]['code']); self::assertSame( [[], ['Seiten: Signatur von et_pb_x() '], ['lit-divi-insert']], self::cellsOf(DiviFeature::PageWrite, $columns, $status) ); self::assertSame( [['Automatisch geprüft'], [], ['lit-divi-insert']], self::cellsOf(DiviFeature::PageRead, $columns, $status) ); self::assertSame( ['lit/divi-presets-assign'], self::cellsOf(DiviFeature::PresetWrite, $columns, $status)[2] ); } public function testTheColumnsNameEverySourceInWordsAndKeepTheOthersAndOtherProfiles(): void { // One column set per table: it keeps the rows of the first matrix it sees. $code = self::cellsOf(DiviFeature::PageRead, self::columns(self::extras()), ReleaseMatrices::released('5.3.0')); $database = self::cellsOf( DiviFeature::PageRead, self::columns(self::extras()), ReleaseStatus::of('5.3.0', MatrixFixtures::released('database')) ); self::assertSame(['Im Plugin als getestet hinterlegt'], $code[0]); self::assertSame(['Von dir auf dieser Website freigegeben'], $database[0]); self::assertSame([['heading' => 'x']], self::extras()->columns([['heading' => 'x']], 'pods')); self::assertCount(4, self::extras()->columns([['heading' => 'x']], 'divi')); } public function testTheColumnsReadTheOverviewOncePerTable(): void { $calls = 0; $overview = new FeatureOverview(static function () use (&$calls): array { ++$calls; return []; }); $columns = self::columns(self::extras('5.3.0', $overview)); $status = ReleaseMatrices::released('5.3.0'); foreach (DiviFeature::cases() as $feature) { self::cellsOf($feature, $columns, $status); } self::assertSame(1, $calls); } }