Files
WP-LIT-MCP/tests/Unit/Admin/LogListTableTest.php
T
janniclas.loosenandClaude Opus 5.5 522f5bbd19 feat: change log tab with filters, revision links and clear button
ChangeLog gains query()/abilities()/clear() (filters: ability, user,
result, date range - all optional and additive). LogTab/LogListTable
render the "Protokoll" admin tab: WP_List_Table with pagination, links
to the post/attachment/nav_menu edit screen by object_type, a revision
link shown only when the user may edit the revision's parent post, and
a nonce+manage_options-gated "Protokoll leeren" with a JS confirmation
and highlighted 'partial' rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 04:12:13 +02:00

204 lines
8.8 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\LogListTable;
use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use WP_Post;
use WP_Term;
final class LogListTableTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Functions\stubEscapeFunctions();
Functions\stubTranslationFunctions();
// Harmless defaults so single_row()'s full column pass doesn't blow up in tests
// that only care about a subset of columns; overridden per test where needed.
Functions\when('get_userdata')->justReturn(false);
Functions\when('post_type_exists')->justReturn(false);
Functions\when('get_post')->justReturn(null);
Functions\when('get_term')->justReturn(null);
Functions\when('current_user_can')->justReturn(false);
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.de/wp-admin/' . $path);
}
protected function tearDown(): void
{
unset($_GET['paged']);
parent::tearDown();
}
public function testPrepareItemsQueriesTheCurrentPageAndSetsPagination(): void
{
$_GET['paged'] = '3';
$log = Mockery::mock(ChangeLog::class);
$log->expects('query')
->with(['ability' => 'lit/content-update', 'user_id' => 0], 50, 100)
->andReturn(['rows' => [['id' => '1']], 'total' => 120]);
$table = new LogListTable($log, ['ability' => 'lit/content-update', 'user_id' => 0]);
$table->prepare_items();
self::assertSame([['id' => '1']], $table->items);
self::assertSame(['total_items' => 120, 'per_page' => 50], $table->pagination_args());
}
public function testColumnDefaultEscapesUntrustedClientAndSummaryText(): void
{
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
$item = ['client' => 'Claude <script>alert(1)</script>', 'summary' => '<b>böse</b> Zusammenfassung'];
self::assertStringNotContainsString('<script>', $this->columnDefault($table, $item, 'client'));
self::assertStringContainsString('&lt;script&gt;', $this->columnDefault($table, $item, 'client'));
self::assertStringNotContainsString('<b>', $this->columnDefault($table, $item, 'summary'));
}
public function testColumnDefaultShowsUserDisplayNameOrFallback(): void
{
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
Functions\when('get_userdata')->justReturn((object) ['display_name' => 'Jan <b>']);
$withUser = $this->columnDefault($table, ['user_id' => 3], 'user');
self::assertStringContainsString('Jan &lt;b&gt;', $withUser);
}
public function testColumnDefaultUserFallsBackToIdWhenUserIsGone(): void
{
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
Functions\when('get_userdata')->justReturn(false);
self::assertSame('#7', $this->columnDefault($table, ['user_id' => 7], 'user'));
}
public function testColumnDefaultLinksTheObjectForAKnownPostType(): void
{
Functions\when('post_type_exists')->justReturn(true);
Functions\when('get_edit_post_link')->justReturn('https://kunde.de/wp-admin/post.php?post=12&action=edit');
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
$html = $this->columnDefault($table, ['object_type' => 'page', 'object_id' => '12'], 'object');
self::assertStringContainsString('href="https://kunde.de/wp-admin/post.php?post=12&action=edit"', $html);
self::assertStringContainsString('page 12', $html);
}
public function testColumnDefaultShowsPlainLabelWhenGetEditPostLinkRefusesIt(): void
{
Functions\when('post_type_exists')->justReturn(true);
Functions\when('get_edit_post_link')->justReturn(null);
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
$html = $this->columnDefault($table, ['object_type' => 'page', 'object_id' => '12'], 'object');
self::assertSame('page 12', $html);
}
public function testColumnDefaultLinksAnAttachmentToTheMediaEditScreen(): void
{
Functions\when('post_type_exists')->justReturn(true);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 5]));
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.de/wp-admin/' . $path);
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
$html = $this->columnDefault($table, ['object_type' => 'attachment', 'object_id' => '5'], 'object');
self::assertStringContainsString('href="https://kunde.de/wp-admin/upload.php?item=5"', $html);
}
public function testColumnDefaultShowsPlainLabelForADeletedAttachment(): void
{
Functions\when('get_post')->justReturn(null);
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
self::assertSame('attachment 5', $this->columnDefault($table, ['object_type' => 'attachment', 'object_id' => '5'], 'object'));
}
public function testColumnDefaultLinksANavMenuToTheMenusScreen(): void
{
Functions\when('get_term')->justReturn(new WP_Term(['term_id' => 3, 'taxonomy' => 'nav_menu']));
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.de/wp-admin/' . $path);
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
$html = $this->columnDefault($table, ['object_type' => 'nav_menu', 'object_id' => '3'], 'object');
self::assertStringContainsString('href="https://kunde.de/wp-admin/nav-menus.php?menu=3"', $html);
}
/** @dataProvider unlinkableObjectTypes */
public function testColumnDefaultNeverLinksSettingsOrMenuLocations(string $type, string $id): void
{
Functions\when('post_type_exists')->justReturn(false);
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
self::assertSame(trim($type . ' ' . $id), $this->columnDefault($table, ['object_type' => $type, 'object_id' => $id], 'object'));
}
/** @return list<list<string>> */
public static function unlinkableObjectTypes(): array
{
return [
['option', 'blogname,blogdescription'],
['menu_location', 'primary'],
['unknown', '42'],
];
}
public function testRevisionColumnShowsADashWithoutARevision(): void
{
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
self::assertSame('–', $this->columnDefault($table, ['revision_id' => 0], 'revision'));
}
public function testRevisionColumnLinksOnlyWhenTheUserCanEditTheParentPost(): void
{
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 811, 'post_type' => 'revision', 'post_parent' => 12]));
Functions\when('current_user_can')->justReturn(true);
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.de/wp-admin/' . $path);
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
$html = $this->columnDefault($table, ['revision_id' => 811], 'revision');
self::assertStringContainsString('href="https://kunde.de/wp-admin/revision.php?revision=811"', $html);
}
public function testRevisionColumnHidesTheLinkWithoutEditPermission(): void
{
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 811, 'post_type' => 'revision', 'post_parent' => 12]));
Functions\when('current_user_can')->justReturn(false);
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
self::assertSame('–', $this->columnDefault($table, ['revision_id' => 811], 'revision'));
}
public function testSingleRowHighlightsPartialResults(): void
{
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0]);
ob_start();
$table->single_row(['result' => 'partial']);
$partial = (string) ob_get_clean();
ob_start();
$table->single_row(['result' => 'ok']);
$ok = (string) ob_get_clean();
self::assertStringContainsString('<tr style=', $partial);
self::assertSame('<tr>', substr($ok, 0, 4));
}
/** @param array<string, mixed> $item */
private function columnDefault(LogListTable $table, array $item, string $column): string
{
return (string) (new \ReflectionMethod($table, 'column_default'))->invoke($table, $item, $column);
}
}