Sichtbarer Name „Kompatibilität“ (Slug compatibility bleibt) in Reiter, Hinweisen, Hilfe-Tab, CLI-Hilfe, Guides und readme. Oben eine Übersicht aller Plugins mit Gesamtzustand, Version, Zustand (Wort und Icon), Freigabe-Art in Klartext und Sprunglink; je Plugin erst Zustand und nächster Schritt, dann Aktionen, dann aufklappbare Details (offen bei Handlungsbedarf, ohne JavaScript). Tabellen mit Beschriftung und Kartenansicht bis 375 px, Buttons mit Plugin-Namen im zugänglichen Namen, Kontraste mit Reserve. Doku: Divi leert beim Prüfen den Cache einmal komplett. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
380 lines
16 KiB
PHP
380 lines
16 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Admin;
|
|
|
|
use Brain\Monkey\Actions;
|
|
use Brain\Monkey\Filters;
|
|
use Brain\Monkey\Functions;
|
|
use LIT\MCP\Admin\CompatibilityTab;
|
|
use LIT\MCP\Compatibility\CheckReport;
|
|
use LIT\MCP\Compatibility\CheckReportStore;
|
|
use LIT\MCP\Compatibility\FeatureStatus;
|
|
use LIT\MCP\Compatibility\ReleaseState;
|
|
use LIT\MCP\Integrations\Divi\Admin\DiviAdminExtras;
|
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
|
use LIT\MCP\Integrations\Divi\Release\FeatureOverview;
|
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
|
use LIT\MCP\Integrations\Divi\Release\WriteProbe;
|
|
use LIT\MCP\Tests\Support\FeatureFakeTool;
|
|
use LIT\MCP\Tests\Support\MatrixFixtures;
|
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
use Mockery;
|
|
use WP_Post;
|
|
|
|
final class DiviAdminExtrasTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Functions\stubEscapeFunctions();
|
|
Functions\stubTranslationFunctions();
|
|
Functions\when('current_user_can')->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() <geändert>'],
|
|
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 „Kompatibilität“', $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('<code>lit_mcp_divi_auto_release</code>', $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 <Admin>']);
|
|
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 <Admin>.', $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<array{heading: string, cell: callable, code?: bool}> */
|
|
private static function columns(DiviAdminExtras $extras, string $id = 'divi'): array
|
|
{
|
|
return $extras->columns([], $id);
|
|
}
|
|
|
|
/** @return list<list<string>> 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() <geändert>'], ['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);
|
|
}
|
|
}
|