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>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
cc72f9b946
commit
1b73f0652a
@@ -241,6 +241,35 @@ final class AccessTabTest extends TestCase
|
||||
self::assertStringContainsString('confirm(', $html);
|
||||
}
|
||||
|
||||
/** The admin script asks before a save that leaves nobody, or the admin themselves, with access. */
|
||||
public function testRolesFormCarriesTheConfirmationTextsAndTheOwnRoles(): void
|
||||
{
|
||||
$this->stubRender(['editor']);
|
||||
|
||||
$html = $this->renderTab([]);
|
||||
|
||||
self::assertStringContainsString(
|
||||
'<form method="post" action="options.php" class="lit-mcp-access-form"'
|
||||
. ' data-confirm-none="Keine Rolle freigegeben – niemand kann LIT MCP nutzen. Trotzdem speichern?"'
|
||||
. ' data-confirm-own="Keine Ihrer Rollen ist mehr freigegeben – Sie selbst können LIT MCP nicht mehr nutzen.'
|
||||
. ' Trotzdem speichern?"'
|
||||
. ' data-own-roles="editor">',
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
public function testMultisiteSuperAdminsGetNoOwnRoleConfirmation(): void
|
||||
{
|
||||
$this->stubRender(['editor']);
|
||||
Functions\when('is_multisite')->justReturn(true);
|
||||
Functions\when('is_super_admin')->justReturn(true);
|
||||
|
||||
$html = $this->renderTab([]);
|
||||
|
||||
self::assertStringContainsString('class="lit-mcp-access-form"', $html);
|
||||
self::assertStringNotContainsString('data-own-roles', $html);
|
||||
}
|
||||
|
||||
public function testRolesAreAFormTableFieldsetWithLegendAndDescription(): void
|
||||
{
|
||||
$this->stubRender(['editor']);
|
||||
|
||||
@@ -104,5 +104,6 @@ final class AdminNoticesTest extends TestCase
|
||||
|
||||
self::assertSame([], $notices->messages(false));
|
||||
self::assertCount(1, $notices->messages(true));
|
||||
self::assertSame([], $notices->messages(true, true), 'not where the status list already says it');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,8 +150,8 @@ final class DiviTabTest extends TestCase
|
||||
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): void {
|
||||
echo '<button>' . $text . '</button>';
|
||||
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');
|
||||
@@ -173,6 +173,21 @@ final class DiviTabTest extends TestCase
|
||||
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'));
|
||||
|
||||
@@ -48,7 +48,6 @@ final class LogListTableTest extends TestCase
|
||||
return new LogListTable(
|
||||
$log ?? Mockery::mock(ChangeLog::class),
|
||||
$filters + ['ability' => '', 'user_id' => 0],
|
||||
null,
|
||||
$toolLabels,
|
||||
$objects,
|
||||
self::NOW
|
||||
@@ -254,18 +253,6 @@ final class LogListTableTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testTopNavRendersTheGivenFilters(): void
|
||||
{
|
||||
$table = new LogListTable(Mockery::mock(ChangeLog::class), ['ability' => '', 'user_id' => 0], static function (): void {
|
||||
echo '<select name="ability"></select>';
|
||||
});
|
||||
|
||||
ob_start();
|
||||
$table->display();
|
||||
|
||||
self::assertStringContainsString('<div class="alignleft actions"><select name="ability"></select></div>', (string) ob_get_clean());
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $item */
|
||||
private function rowActions(LogListTable $table, array $item): string
|
||||
{
|
||||
|
||||
@@ -184,15 +184,16 @@ final class LogTabTest extends TestCase
|
||||
self::assertFalse($tab->savePerPage(false, 'other_per_page', '40'));
|
||||
}
|
||||
|
||||
public function testRenderShowsFiltersAndAConfirmProtectedClearButton(): void
|
||||
public function testRenderShowsTheFiltersAboveTheTableOutsideTheTableNav(): void
|
||||
{
|
||||
$html = $this->render(['lit/content-update', 'lit/media-upload-from-url', 'lit/x"><script>']);
|
||||
|
||||
self::assertStringContainsString('name="result"', $html);
|
||||
self::assertStringNotContainsString('<script>', $html);
|
||||
self::assertStringContainsString('lit_mcp_clear_log', $html);
|
||||
self::assertStringContainsString('confirm(', $html);
|
||||
self::assertStringContainsString('nonce-lit_mcp_clear_log', $html);
|
||||
// Core hides ".tablenav .actions" on phones; the filters live in their own block before the table.
|
||||
self::assertStringContainsString('<div class="lit-mcp-log-filters">', $html);
|
||||
self::assertStringNotContainsString('alignleft actions', $html);
|
||||
self::assertStringNotContainsString('lit_mcp_clear_log', $html, 'nothing to clear in an empty log');
|
||||
|
||||
// One intro line; the retention days link to the Limits tab.
|
||||
self::assertMatchesRegularExpression('#<p class="description">[^<]*<a href="[^"]*tab=limits">90 Tagen</a>#', $html);
|
||||
@@ -207,6 +208,22 @@ final class LogTabTest extends TestCase
|
||||
self::assertStringNotContainsString('Filter zurücksetzen', $html, 'no reset without an active filter');
|
||||
}
|
||||
|
||||
public function testClearButtonAppearsOnlyWithEntriesAndNamesTheirCount(): void
|
||||
{
|
||||
$two = $this->render([], 2);
|
||||
|
||||
self::assertStringContainsString('<div class="lit-mcp-clear-log">', $two);
|
||||
self::assertStringContainsString('lit_mcp_clear_log', $two);
|
||||
self::assertStringContainsString('confirm(', $two);
|
||||
self::assertStringContainsString('nonce-lit_mcp_clear_log', $two);
|
||||
self::assertStringContainsString('>Protokoll leeren (2 Einträge)</button>', $two);
|
||||
self::assertStringContainsString('>Protokoll leeren (1 Eintrag)</button>', $this->render([], 1));
|
||||
|
||||
// The count is the whole log, not the filtered page (query() above answers with no rows).
|
||||
$_GET['result'] = 'refused';
|
||||
self::assertStringContainsString('>Protokoll leeren (2 Einträge)</button>', $this->render([], 2));
|
||||
}
|
||||
|
||||
public function testToolFilterShowsHumanLabelsGroupedByIntegration(): void
|
||||
{
|
||||
$_GET['ability'] = 'lit/content-update';
|
||||
@@ -234,7 +251,7 @@ final class LogTabTest extends TestCase
|
||||
}
|
||||
|
||||
/** @param list<string> $abilities abilities found in the log */
|
||||
private function render(array $abilities): string
|
||||
private function render(array $abilities, int $total = 0): string
|
||||
{
|
||||
Functions\stubEscapeFunctions();
|
||||
Functions\stubTranslationFunctions();
|
||||
@@ -267,6 +284,7 @@ final class LogTabTest extends TestCase
|
||||
$log = Mockery::mock(ChangeLog::class);
|
||||
$log->allows('abilities')->andReturn($abilities);
|
||||
$log->allows('query')->andReturn(['rows' => [], 'total' => 0]);
|
||||
$log->allows('count')->andReturn($total);
|
||||
|
||||
ob_start();
|
||||
$this->tab($log)->render();
|
||||
|
||||
@@ -200,7 +200,7 @@ final class ToolsTabTest extends TestCase
|
||||
|
||||
self::assertStringContainsString('name="lit_mcp_exposure[@readonly:divi]" value="1" checked="checked"', $html);
|
||||
$update = $this->row($html, 'lit/divi-update');
|
||||
self::assertStringContainsString('<select name="lit_mcp_exposure[lit/divi-update]" disabled', $update);
|
||||
self::assertMatchesRegularExpression('#<select name="lit_mcp_exposure\[lit/divi-update\]"[^>]* disabled#', $update);
|
||||
self::assertStringContainsString('Durch „Nur lesen“ aus', $update);
|
||||
self::assertStringContainsString(
|
||||
'<input type="hidden" name="lit_mcp_exposure[lit/divi-update]" value="indirect">',
|
||||
@@ -211,6 +211,36 @@ final class ToolsTabTest extends TestCase
|
||||
self::assertStringNotContainsString('Nur lesen', $outline);
|
||||
}
|
||||
|
||||
/** The admin script mirrors a group's mode and "nur lesen" switch onto its rows before saving. */
|
||||
public function testMarkupCarriesWhatTheScriptNeedsForLiveGroupFeedback(): void
|
||||
{
|
||||
$html = $this->render([]);
|
||||
|
||||
self::assertStringContainsString('<div class="lit-mcp-groups" data-like-group="wie Gruppe (%s)">', $html);
|
||||
self::assertStringContainsString('class="lit-mcp-group-mode"', $this->select($html, '@group:divi'));
|
||||
self::assertStringContainsString('class="lit-mcp-group-readonly"', $this->group($html, 'Divi'));
|
||||
$update = $this->select($html, 'lit/divi-update');
|
||||
self::assertStringContainsString('class="lit-mcp-tool-mode"', $update);
|
||||
self::assertStringContainsString('data-default-label="Standard (direkt)"', $update);
|
||||
self::assertStringContainsString('data-writes="1"', $update);
|
||||
self::assertStringNotContainsString('data-writes', $this->select($html, 'lit/divi-outline'));
|
||||
self::assertStringContainsString('data-required="1"', $this->select($html, 'lit/guide'));
|
||||
self::assertStringContainsString('data-direct-only="1"', $this->select($html, 'lit/media-preview'));
|
||||
self::assertStringNotContainsString('data-direct-only', $update);
|
||||
// Every write row carries the "nur lesen" note, hidden until the switch is on.
|
||||
self::assertStringContainsString(
|
||||
'<p class="description lit-mcp-readonly-note" hidden>Durch „Nur lesen“ aus</p>',
|
||||
$this->row($html, 'lit/divi-update')
|
||||
);
|
||||
self::assertStringNotContainsString('lit-mcp-readonly-note', $this->row($html, 'lit/divi-outline'));
|
||||
|
||||
$locked = $this->render(['@readonly:divi' => true]);
|
||||
self::assertStringContainsString(
|
||||
'<p class="description lit-mcp-readonly-note">Durch „Nur lesen“ aus</p>',
|
||||
$this->row($locked, 'lit/divi-update')
|
||||
);
|
||||
}
|
||||
|
||||
public function testCountsDirectToolsUnderTheSavedSettings(): void
|
||||
{
|
||||
$html = $this->render(['@group:divi' => 'disabled']);
|
||||
@@ -338,7 +368,7 @@ final class ToolsTabTest extends TestCase
|
||||
$html = $this->render([]);
|
||||
$divi = $this->group($html, 'Divi');
|
||||
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-divi" class="postbox lit-mcp-group">', $divi);
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-divi" class="postbox lit-mcp-group" data-group="divi">', $divi);
|
||||
self::assertStringContainsString('<div class="postbox-header">', $divi);
|
||||
self::assertStringContainsString('<button type="button" class="handlediv" aria-expanded="true">', $divi);
|
||||
self::assertStringContainsString('<span class="screen-reader-text">Gruppe ein-/ausblenden: Divi</span>', $divi);
|
||||
@@ -367,11 +397,11 @@ final class ToolsTabTest extends TestCase
|
||||
$default = $this->render(['@group:divi' => 'disabled']);
|
||||
$saved = $this->render(['@group:divi' => 'disabled'], null, ['lit-mcp-group-media']);
|
||||
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-divi" class="postbox lit-mcp-group closed">', $default);
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-divi" class="postbox lit-mcp-group closed" data-group="divi">', $default);
|
||||
self::assertStringContainsString('<button type="button" class="handlediv" aria-expanded="false">', $this->group($default, 'Divi'));
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-media" class="postbox lit-mcp-group">', $default);
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-media" class="postbox lit-mcp-group" data-group="media">', $default);
|
||||
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-divi" class="postbox lit-mcp-group">', $saved);
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-media" class="postbox lit-mcp-group closed">', $saved);
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-divi" class="postbox lit-mcp-group" data-group="divi">', $saved);
|
||||
self::assertStringContainsString('<div id="lit-mcp-group-media" class="postbox lit-mcp-group closed" data-group="media">', $saved);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user