Files
WP-LIT-MCP/tests/Unit/Admin/DiviTabTest.php
T
Jan-Niclas LoosenandClaude Fable 5.1 1b73f0652a fix: Admin-UX aus dem Review – Live-Gruppenfeedback, Protokollfilter, Divi-Hinweis, Rollen-Rückfrage
- 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>
2026-10-08 16:39:44 +02:00

258 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}