- Tools: „Ganze Gruppe“ und „Nur lesen“ wirken sofort auf die Zeilen (Option „wie Gruppe (…)“, gesperrte Selects mit Hinweis), der Server bleibt die Wahrheit; Markup trägt die nötigen data-Attribute. - Protokoll: Filter in eigenem Block über der Tabelle (Core blendet .tablenav .actions auf Telefonen aus); „Protokoll leeren“ nur bei Einträgen, rechtsbündig, mit Gesamtzahl über ChangeLog::count(). - Divi: Beschreibung unter „Prüfung starten“, Button primär solange ungetestet ohne frischen Bericht. - Zugriff: Rückfrage vor dem Speichern ohne Rolle oder ohne eigene Rolle. - Verbindung: HTTPS-Hinweis nicht doppelt neben der Statusliste. - Mobil: breitere Einstellungs-Spalte im Tools-Reiter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
258 lines
10 KiB
PHP
258 lines
10 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Tests\Unit\Admin;
|
||
|
||
use Brain\Monkey\Functions;
|
||
use LIT\MCP\Admin\AdminPostAction;
|
||
use LIT\MCP\Admin\DiviTab;
|
||
use LIT\MCP\Integrations\Divi\Release\CheckReport;
|
||
use LIT\MCP\Integrations\Divi\Release\CheckReportStore;
|
||
use LIT\MCP\Integrations\Divi\Release\CheckStep;
|
||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||
use LIT\MCP\Integrations\Divi\Release\ReleaseStore;
|
||
use LIT\MCP\Plugin;
|
||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||
use LIT\MCP\Tests\Unit\TestCase;
|
||
use Mockery;
|
||
|
||
final class DiviTabTest extends TestCase
|
||
{
|
||
private ?string $redirect = null;
|
||
|
||
protected function setUp(): void
|
||
{
|
||
parent::setUp();
|
||
$this->redirect = null;
|
||
Functions\stubEscapeFunctions();
|
||
Functions\stubTranslationFunctions();
|
||
Functions\when('current_user_can')->justReturn(true);
|
||
Functions\when('check_admin_referer')->justReturn(1);
|
||
Functions\when('get_current_user_id')->justReturn(1);
|
||
Functions\when('wp_safe_redirect')->alias(function (string $url): bool {
|
||
$this->redirect = $url;
|
||
|
||
return 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)
|
||
);
|
||
}
|
||
|
||
private static function report(bool $green = true, string $version = '5.3.0', string $key = 'key'): CheckReport
|
||
{
|
||
$steps = array_map(
|
||
static fn (string $step): CheckStep => new CheckStep($step, $green ? [] : ['rot']),
|
||
[CheckStep::MANIFEST, CheckStep::MIRRORS, CheckStep::CATALOG, CheckStep::FORMAT, CheckStep::WRITE]
|
||
);
|
||
|
||
return new CheckReport('r1', '2026-10-01T08:00:00+00:00', 1, $version, Plugin::VERSION, $key, $steps);
|
||
}
|
||
|
||
private function tab(?CheckReport $report, ReleaseStore $releases, ?FakeChangeLog $log = null): DiviTab
|
||
{
|
||
$reports = Mockery::mock(CheckReportStore::class);
|
||
$reports->allows('latest')->andReturn($report);
|
||
|
||
return new DiviTab(
|
||
$reports,
|
||
$releases,
|
||
$log ?? FakeChangeLog::forbidden(),
|
||
new AdminPostAction(static function (): void {
|
||
}),
|
||
static fn (): string => '5.3.0',
|
||
static fn (string $version): string => 'key'
|
||
);
|
||
}
|
||
|
||
public function testReleaseNeedsAFreshGreenReport(): void
|
||
{
|
||
$reports = [null, self::report(false), self::report(true, '5.2.9'), self::report(true, '5.3.0', 'old')];
|
||
foreach ($reports as $report) {
|
||
$this->redirect = null;
|
||
$releases = Mockery::mock(ReleaseStore::class);
|
||
$releases->expects('add')->never();
|
||
|
||
$this->tab($report, $releases)->release();
|
||
|
||
self::assertStringContainsString('lit_mcp_notice=divi_release_refused', (string) $this->redirect);
|
||
}
|
||
}
|
||
|
||
public function testReleaseStoresTheEntryAndLogsIt(): void
|
||
{
|
||
$releases = Mockery::mock(ReleaseStore::class);
|
||
$releases->expects('add')->once()->with('5.3.0', Plugin::VERSION, 1, 'r1');
|
||
$log = new FakeChangeLog();
|
||
|
||
$this->tab(self::report(), $releases, $log)->release();
|
||
|
||
self::assertStringContainsString('lit_mcp_notice=divi_released', (string) $this->redirect);
|
||
self::assertSame('lit/divi-release', $log->records[0]['ability']);
|
||
self::assertStringContainsString('Divi 5.3.0', $log->records[0]['summary']);
|
||
}
|
||
|
||
public function testWithdrawRemovesTheEntryOfTheInstalledVersion(): void
|
||
{
|
||
$releases = Mockery::mock(ReleaseStore::class);
|
||
$releases->expects('withdraw')->once()->with('5.3.0', Plugin::VERSION)->andReturn(true);
|
||
$log = new FakeChangeLog();
|
||
|
||
$this->tab(null, $releases, $log)->withdraw();
|
||
|
||
self::assertStringContainsString('lit_mcp_notice=divi_withdrawn', (string) $this->redirect);
|
||
self::assertSame('lit/divi-release', $log->records[0]['ability']);
|
||
}
|
||
|
||
public function testActionsNeedNonceAndManageOptions(): void
|
||
{
|
||
Functions\when('current_user_can')->justReturn(false);
|
||
Functions\expect('wp_die')->twice()->andReturnUsing(static function (): never {
|
||
throw new \RuntimeException('died');
|
||
});
|
||
$releases = Mockery::mock(ReleaseStore::class);
|
||
$releases->expects('add')->never();
|
||
$releases->expects('withdraw')->never();
|
||
$tab = $this->tab(self::report(), $releases);
|
||
|
||
foreach (['release', 'withdraw'] as $method) {
|
||
try {
|
||
$tab->{$method}();
|
||
self::fail($method . ' without manage_options');
|
||
} catch (\RuntimeException $e) {
|
||
self::assertSame('died', $e->getMessage());
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
public function testReleaseChecksTheNonce(): void
|
||
{
|
||
Functions\when('check_admin_referer')->alias(static function (string $action): never {
|
||
throw new \RuntimeException('bad nonce for ' . $action);
|
||
});
|
||
$releases = Mockery::mock(ReleaseStore::class);
|
||
$releases->expects('add')->never();
|
||
|
||
$this->expectExceptionMessage('bad nonce for lit_mcp_divi_release');
|
||
$this->tab(self::report(), $releases)->release();
|
||
}
|
||
|
||
/** @return string The rendered tab. */
|
||
private function rendered(ReleaseStatus $status, ?CheckReport $report): string
|
||
{
|
||
ReleaseGate::useResolver(static fn (): ReleaseStatus => $status);
|
||
Functions\when('get_posts')->justReturn([]);
|
||
Functions\when('rest_url')->justReturn('https://k.de/wp-json/lit-mcp/v1/divi-check');
|
||
Functions\when('wp_create_nonce')->justReturn('n');
|
||
Functions\when('wp_nonce_field')->justReturn('');
|
||
Functions\when('submit_button')->alias(static function (string $text, string $type = 'primary'): void {
|
||
echo '<button class="' . $type . '">' . $text . '</button>';
|
||
});
|
||
Functions\when('get_userdata')->justReturn((object) ['display_name' => 'Jan']);
|
||
Functions\when('date_i18n')->justReturn('01.10.2026 10:00');
|
||
Functions\when('get_option')->justReturn('d.m.Y');
|
||
|
||
ob_start();
|
||
$this->tab($report, Mockery::mock(ReleaseStore::class))->render();
|
||
|
||
return (string) ob_get_clean();
|
||
}
|
||
|
||
public function testUntestedWithAFreshGreenReportOffersTheRelease(): void
|
||
{
|
||
$html = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report());
|
||
|
||
self::assertStringContainsString('ungetestet', $html);
|
||
self::assertStringContainsString('Prüfung starten', $html);
|
||
self::assertStringContainsString('Divi 5.3.0 freigeben', $html);
|
||
self::assertStringContainsString('name="_wpnonce" value="n"', $html);
|
||
}
|
||
|
||
public function testCheckButtonSaysWhatItDoesAndLeadsWhileNothingElseCanBeDone(): void
|
||
{
|
||
$stale = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report(true, '5.2.9'));
|
||
$fresh = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report());
|
||
$released = $this->rendered(ReleaseStatus::released('5.3.0'), self::report());
|
||
|
||
foreach ([$stale, $fresh, $released] as $html) {
|
||
self::assertStringContainsString('legt eine private Testseite an', $html);
|
||
}
|
||
self::assertStringContainsString('<button class="primary">Prüfung starten</button>', $stale);
|
||
self::assertStringContainsString('<button class="secondary">Prüfung starten</button>', $fresh);
|
||
self::assertStringContainsString('<button class="primary">Divi 5.3.0 freigeben</button>', $fresh);
|
||
self::assertStringContainsString('<button class="secondary">Prüfung starten</button>', $released);
|
||
}
|
||
|
||
public function testUntestedWithAStaleReportAsksForANewCheck(): void
|
||
{
|
||
$html = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report(true, '5.2.9'));
|
||
|
||
self::assertStringNotContainsString('Divi 5.3.0 freigeben', $html);
|
||
self::assertStringContainsString('Prüfung erneut ausführen', $html);
|
||
}
|
||
|
||
public function testReleasedFromCodeOffersNeitherReleaseNorWithdrawal(): void
|
||
{
|
||
$html = $this->rendered(ReleaseStatus::released('5.3.0'), self::report());
|
||
|
||
self::assertStringContainsString('freigegeben', $html);
|
||
self::assertStringContainsString('im Plugin als getestet hinterlegt', $html);
|
||
self::assertStringNotContainsString('freigeben</button>', $html);
|
||
self::assertStringNotContainsString('Freigabe zurückziehen', $html);
|
||
}
|
||
|
||
public function testADatabaseReleaseCanBeWithdrawn(): void
|
||
{
|
||
$status = new ReleaseStatus(ReleaseState::Released, '5.3.0', ReleaseStatus::SOURCE_DATABASE, [], [
|
||
'released_at' => '2026-10-01T08:00:00+00:00', 'user_id' => 1,
|
||
]);
|
||
|
||
self::assertStringContainsString('Freigabe zurückziehen', $this->rendered($status, self::report()));
|
||
}
|
||
|
||
public function testWithoutDiviShowsOnlyAShortNotice(): void
|
||
{
|
||
ReleaseGate::useResolver(static function (): ReleaseStatus {
|
||
self::fail('resolver called although Divi is not active');
|
||
});
|
||
$reports = Mockery::mock(CheckReportStore::class);
|
||
$reports->expects('latest')->never();
|
||
$tab = new DiviTab(
|
||
$reports,
|
||
Mockery::mock(ReleaseStore::class),
|
||
FakeChangeLog::forbidden(),
|
||
new AdminPostAction(static function (): void {
|
||
}),
|
||
static fn (): string => '',
|
||
static fn (string $version): string => 'key'
|
||
);
|
||
|
||
ob_start();
|
||
$tab->render();
|
||
$html = (string) ob_get_clean();
|
||
|
||
self::assertStringContainsString(
|
||
'Divi ist auf dieser Website nicht aktiv; die Divi-Freigabe ist ohne Bedeutung.',
|
||
$html
|
||
);
|
||
self::assertStringNotContainsString('Prüfung starten', $html);
|
||
}
|
||
|
||
public function testIncompatibleListsTheProblems(): void
|
||
{
|
||
$html = $this->rendered(
|
||
new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['Theme Builder: Nachbau von x() – y']),
|
||
null
|
||
);
|
||
|
||
self::assertStringContainsString('inkompatibel', $html);
|
||
self::assertStringContainsString('Theme Builder: Nachbau von x() – y', $html);
|
||
self::assertStringContainsString('Plugin-Update', $html);
|
||
}
|
||
}
|