Files
janniclas.loosenandClaude Opus 5.5 0f23192873 feat: Protokoll tab as a core-style list table
The change summary is the wide primary column (key "title", so it cannot
be hidden) with the tool's human label below and row actions "Revision
ansehen" / "Objekt öffnen". Objects get human names ("Seite: Kontakt",
"Menü: …", "Einstellungen: Titel der Website"; deleted/trashed marked),
primed per page with one query each for posts, menus and users. Times are
relative within 24 h. Non-ok results show a badge and a marked row instead
of an Ergebnis column. Page size and columns via Screen Options; the tool
filter shows labels grouped by integration, dates have visible labels,
and an active filter offers "Filter zurücksetzen" (also in the empty state).

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

168 lines
7.3 KiB
PHP
Raw Permalink 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\LogObjects;
use LIT\MCP\Tests\Unit\TestCase;
use WP_Post;
use WP_Term;
final class LogObjectsTest extends TestCase
{
/** @var array<int, WP_Post> */
private array $posts = [];
protected function setUp(): void
{
parent::setUp();
Functions\stubEscapeFunctions();
Functions\stubTranslationFunctions();
Functions\when('post_type_exists')->alias(static fn (string $type): bool => in_array($type, ['page', 'post', 'attachment'], true));
Functions\when('get_post_type_object')->alias(static fn (string $type): object => (object) [
'labels' => (object) ['singular_name' => ['page' => 'Seite', 'post' => 'Beitrag'][$type] ?? $type],
]);
Functions\when('get_post')->alias(fn ($id) => $this->posts[(int) $id] ?? null);
Functions\when('get_edit_post_link')->alias(
static fn ($id, $context = 'display'): string => 'https://kunde.de/wp-admin/post.php?post=' . $id . '&action=edit'
);
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.de/wp-admin/' . $path);
Functions\when('current_user_can')->justReturn(true);
Functions\when('get_registered_nav_menus')->justReturn(['primary-menu' => 'Hauptmenü']);
}
public function testAPageShowsItsTypeLabelAndLinkedTitle(): void
{
$this->posts[834] = new WP_Post(['ID' => 834, 'post_type' => 'page', 'post_status' => 'publish', 'post_title' => '[Test] <b>Kontakt</b>']);
$object = (new LogObjects())->describe('page', '834');
self::assertSame(
'Seite: <a href="https://kunde.de/wp-admin/post.php?post=834&action=edit">[Test] &lt;b&gt;Kontakt&lt;/b&gt;</a>',
$object['html']
);
self::assertSame('https://kunde.de/wp-admin/post.php?post=834&action=edit', $object['url']);
}
public function testAnUntitledPostSaysSo(): void
{
$this->posts[5] = new WP_Post(['ID' => 5, 'post_type' => 'post', 'post_status' => 'draft', 'post_title' => '']);
self::assertStringContainsString('>(kein Titel)</a>', (new LogObjects())->describe('post', '5')['html']);
}
public function testNoLinkWhenTheUserMayNotEditThePost(): void
{
Functions\when('get_edit_post_link')->justReturn(null);
$this->posts[834] = new WP_Post(['ID' => 834, 'post_type' => 'page', 'post_status' => 'publish', 'post_title' => 'Kontakt']);
self::assertSame(['html' => 'Seite: Kontakt', 'url' => ''], (new LogObjects())->describe('page', '834'));
}
public function testATrashedPostIsNamedButNotLinked(): void
{
$this->posts[783] = new WP_Post(['ID' => 783, 'post_type' => 'page', 'post_status' => 'trash', 'post_title' => 'LIT Smoke Seite']);
self::assertSame(
['html' => 'Seite: LIT Smoke Seite (im Papierkorb)', 'url' => ''],
(new LogObjects())->describe('page', '783')
);
}
public function testADeletedPostShowsItsIdAsDeleted(): void
{
self::assertSame(['html' => 'Seite: #834 (gelöscht)', 'url' => ''], (new LogObjects())->describe('page', '834'));
}
public function testAnAttachmentIsAMediumLinkedToTheMediaEditScreen(): void
{
$this->posts[830] = new WP_Post(['ID' => 830, 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_title' => 'Massivholzmoebel']);
$object = (new LogObjects())->describe('attachment', '830');
self::assertSame(
'Medium: <a href="https://kunde.de/wp-admin/post.php?post=830&action=edit">Massivholzmoebel</a>',
$object['html']
);
self::assertSame('Medium: #799 (gelöscht)', (new LogObjects())->describe('attachment', '799')['html']);
}
public function testANavMenuIsLinkedToTheMenusScreen(): void
{
Functions\when('get_terms')->justReturn([new WP_Term(['term_id' => 21, 'name' => 'Haupt<menü>', 'taxonomy' => 'nav_menu'])]);
$objects = new LogObjects();
$objects->prime([['object_type' => 'nav_menu', 'object_id' => '21'], ['object_type' => 'nav_menu', 'object_id' => '22']]);
self::assertSame(
[
'html' => 'Menü: <a href="https://kunde.de/wp-admin/nav-menus.php?action=edit&menu=21">Haupt&lt;menü&gt;</a>',
'url' => 'https://kunde.de/wp-admin/nav-menus.php?action=edit&menu=21',
],
$objects->describe('nav_menu', '21')
);
self::assertSame(['html' => 'Menü: #22 (gelöscht)', 'url' => ''], $objects->describe('nav_menu', '22'));
}
public function testANavMenuIsNotLinkedWithoutTheMenuCapability(): void
{
Functions\when('current_user_can')->justReturn(false);
Functions\when('get_terms')->justReturn([new WP_Term(['term_id' => 21, 'name' => 'Hauptmenü', 'taxonomy' => 'nav_menu'])]);
$objects = new LogObjects();
$objects->prime([['object_type' => 'nav_menu', 'object_id' => '21']]);
self::assertSame(['html' => 'Menü: Hauptmenü', 'url' => ''], $objects->describe('nav_menu', '21'));
}
public function testAMenuLocationShowsItsRegisteredLabel(): void
{
self::assertSame(['html' => 'Menüposition: Hauptmenü', 'url' => ''], (new LogObjects())->describe('menu_location', 'primary-menu'));
self::assertSame(
['html' => 'Menüposition: <code>footer&lt;x&gt;</code>', 'url' => ''],
(new LogObjects())->describe('menu_location', 'footer<x>')
);
}
public function testOptionsShowHumanLabelsAndUnknownKeysAsCode(): void
{
self::assertSame(
['html' => 'Einstellungen: Titel der Website, Untertitel', 'url' => ''],
(new LogObjects())->describe('option', 'blogname,blogdescription')
);
self::assertSame(
['html' => 'Einstellung: <code>foo&lt;b&gt;</code>', 'url' => ''],
(new LogObjects())->describe('option', 'foo<b>')
);
}
public function testAnUnknownObjectTypeIsShownAsCode(): void
{
self::assertSame(['html' => '<code>shop_thing 42</code>', 'url' => ''], (new LogObjects())->describe('shop_thing', '42'));
self::assertSame(['html' => '–', 'url' => ''], (new LogObjects())->describe('', ''));
}
public function testPrimeLoadsAllPostsAndMenusOfThePageInOneGoEach(): void
{
Functions\expect('_prime_post_caches')->once()->with([834, 830], false, false);
Functions\expect('get_terms')->once()->with(['taxonomy' => 'nav_menu', 'include' => [21], 'hide_empty' => false])->andReturn([]);
(new LogObjects())->prime([
['object_type' => 'page', 'object_id' => '834'],
['object_type' => 'attachment', 'object_id' => '830'],
['object_type' => 'page', 'object_id' => '834'],
['object_type' => 'nav_menu', 'object_id' => '21'],
['object_type' => 'option', 'object_id' => 'blogname'],
['object_type' => 'page', 'object_id' => 'x'],
]);
}
public function testPrimeQueriesNothingWithoutPostsOrMenus(): void
{
Functions\expect('_prime_post_caches')->never();
Functions\expect('get_terms')->never();
(new LogObjects())->prime([['object_type' => 'option', 'object_id' => 'blogname']]);
}
}