feat(admin): Einstellungsseite überarbeitet, auch für das Handy

Verbindung: Probleme vor der Anleitung. Tools: Suche, Filter, Alle ein-/ausklappen, Zurücksetzen je Gruppe.
Divi-Reiter in Klartext mit Statussymbolen. Rollenhinweise, Hilfe-Tab, Reiter als Raster und Karten auf dem Handy,
Kontrast, Tippflächen, einheitliche Du-Anrede. Bestätigungen über data-lit-mcp-confirm statt Inline-onclick.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-05 16:36:16 +01:00
co-authored by Claude Sonnet 5.5
parent 165e61d7fe
commit 96fa58188b
16 changed files with 865 additions and 84 deletions
+11 -4
View File
@@ -185,7 +185,7 @@ final class AccessTabTest extends TestCase
Functions\when('settings_fields')->justReturn(null);
Functions\when('submit_button')->alias(static function ($text = null, $type = 'primary', $name = 'submit', $wrap = true, $other = ''): void {
$attrs = is_array($other) ? implode(' ', array_map(
static fn ($k, $v): string => sprintf('%s="%s"', $k, $v),
static fn ($k, $v): string => sprintf('%s="%s"', $k, htmlspecialchars((string) $v, ENT_QUOTES)),
array_keys($other),
$other
)) : '';
@@ -238,7 +238,14 @@ final class AccessTabTest extends TestCase
self::assertStringContainsString('name="user_id" value="3"', $html);
self::assertStringContainsString('value="lit_mcp_revoke"', $html);
self::assertStringContainsString('nonce-lit_mcp_revoke_uuid-1', $html);
self::assertStringContainsString('confirm(', $html);
self::assertStringContainsString(
'data-lit-mcp-confirm="Verbindung „Claude &lt;script&gt;“ von redaktion&lt;b&gt; widerrufen?',
$html
);
self::assertStringContainsString('aria-label="Widerrufen: Claude &lt;script&gt;, redaktion&lt;b&gt;"', $html);
self::assertStringContainsString('id="lit-mcp-revoke-uuid-1"', $html);
self::assertStringContainsString('aria-describedby="lit-mcp-role-editor"', $html);
self::assertStringNotContainsString('onclick', $html);
}
/** The admin script asks before a save that leaves nobody, or the admin themselves, with access. */
@@ -251,7 +258,7 @@ final class AccessTabTest extends TestCase
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.'
. ' data-confirm-own="Keine deiner Rollen ist mehr freigegeben. Du kannst LIT MCP dann selbst nicht mehr nutzen.'
. ' Trotzdem speichern?"'
. ' data-own-roles="editor">',
$html
@@ -293,7 +300,7 @@ final class AccessTabTest extends TestCase
self::assertStringContainsString('d.m.Y H:i@1790000000', $html);
self::assertStringContainsString('d.m.Y@1790050000', $html);
self::assertStringContainsString('<td class="column-last-used">–</td>', $html);
self::assertStringContainsString('<td class="column-last-used">Noch nie</td>', $html);
}
public function testRenderWithoutConnectionsShowsAnEmptyStateRowLinkingToTheConnectionTab(): void
+29 -1
View File
@@ -51,7 +51,7 @@ final class ConnectionTabTest extends TestCase
);
self::assertStringContainsString('<label for="lit-mcp-url"', $html);
self::assertStringContainsString(
'<button type="button" class="button lit-mcp-copy" data-clipboard-target="#lit-mcp-url">URL kopieren</button>',
'<button type="button" class="button lit-mcp-copy hide-if-no-js" data-clipboard-target="#lit-mcp-url">URL kopieren</button>',
$html
);
self::assertStringContainsString('<span class="success hidden" aria-hidden="true">Kopiert!</span>', $html);
@@ -74,4 +74,32 @@ final class ConnectionTabTest extends TestCase
self::assertStringContainsString('<span class="screen-reader-text">Problem:</span>', $html);
self::assertStringContainsString('href="https://kunde.de/wp-admin/site-health.php"', $html);
}
public function testAFailedCheckShowsAboveTheStepsNotOnlyInTheList(): void
{
$html = $this->render([
['label' => 'HTTPS', 'ok' => false, 'message' => 'Die Website läuft ohne HTTPS.'],
['label' => 'Discovery', 'ok' => null, 'message' => 'noch nicht geprüft'],
]);
self::assertStringContainsString('<div class="notice notice-error inline">', $html);
self::assertStringContainsString('<li>HTTPS: Die Website läuft ohne HTTPS.</li>', $html);
self::assertStringNotContainsString('<li>Discovery:', $html);
self::assertLessThan(strpos($html, '<ol class="lit-mcp-steps">'), strpos($html, 'notice-error'));
}
public function testNoProblemNoticeWhenNothingFailed(): void
{
$html = $this->render([['label' => 'HTTPS', 'ok' => true, 'message' => 'aktiv']]);
self::assertStringNotContainsString('notice-error', $html);
}
public function testExplainsWhereTheAssistantMenuIsWithoutNamingAProduct(): void
{
$html = $this->render([]);
self::assertStringContainsString('<details class="lit-mcp-hint"><summary>Wo finde ich das?</summary>', $html);
self::assertStringContainsString('<label for="lit-mcp-url">Server-URL</label>', $html);
}
}
+1
View File
@@ -55,6 +55,7 @@ final class LimitsTabTest extends TestCase
self::assertStringContainsString('Höchstens 32', $html);
self::assertStringContainsString('10 MB', $html);
self::assertStringContainsString('min="1" max="3650"', $html);
self::assertStringContainsString('Zwischen 1 und 3650 Tagen.', $html);
}
public function testWarnsWhenUploadMbExceedsTheServersOwnUploadLimit(): void
+1 -1
View File
@@ -214,7 +214,7 @@ final class LogTabTest extends TestCase
self::assertStringContainsString('<div class="lit-mcp-clear-log">', $two);
self::assertStringContainsString('lit_mcp_clear_log', $two);
self::assertStringContainsString('confirm(', $two);
self::assertStringContainsString('data-lit-mcp-confirm="Das gesamte Protokoll unwiderruflich löschen?"', $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));
+21
View File
@@ -204,4 +204,25 @@ final class SettingsPageTest extends TestCase
['divi_withdraw_failed', 'error', 'keine Freigabe'],
];
}
public function testTheNoticeParameterIsRemovedFromTheAddressBarAfterwards(): void
{
$page = new SettingsPage([$this->tab('verbindung', 'Verbindung')]);
self::assertSame(['a', 'lit_mcp_notice'], $page->removableQueryArgs(['a']));
}
public function testAddsAnOverviewHelpTab(): void
{
Functions\stubEscapeFunctions();
Functions\stubTranslationFunctions();
$screen = Mockery::mock();
$screen->expects('add_help_tab')->once()->with(Mockery::on(
static fn (array $tab): bool => $tab['id'] === 'lit-mcp-overview'
&& str_contains($tab['content'], 'Tab „Verbindung“')
&& str_contains($tab['content'], 'Tab „Tools“')
));
(new SettingsPage([$this->tab('verbindung', 'Verbindung')]))->helpTabFor($screen);
}
}
+33
View File
@@ -354,6 +354,39 @@ final class ToolsTabTest extends TestCase
self::assertStringNotContainsString('Divi-Code-Modul', $withoutDivi);
}
public function testReconnectHintIsAVisibleNoticeNotGreyFinePrint(): void
{
$html = $this->render([]);
self::assertStringContainsString(
'<div class="notice notice-info inline"><p><strong>Änderungen gelten ab der nächsten Verbindung.</strong>',
$html
);
}
public function testOffersSearchOnlyChangedOpenCloseAndAGroupResetForTheScript(): void
{
$html = $this->render([]);
self::assertStringContainsString('class="lit-mcp-toolbar hide-if-no-js"', $html);
self::assertStringContainsString('<input type="search" id="lit-mcp-tool-search"', $html);
self::assertStringContainsString('lit-mcp-only-changed', $html);
self::assertStringContainsString('lit-mcp-collapse-all', $html);
self::assertStringContainsString('lit-mcp-expand-all', $html);
self::assertSame(3, substr_count($html, 'class="button-link lit-mcp-group-reset hide-if-no-js"'));
}
public function testPhoneLegendNamesTheIconsOfTheArtColumn(): void
{
$html = $this->render([]);
self::assertMatchesRegularExpression(
'#lit-mcp-kind-legend">.*dashicons-visibility.*liest.*dashicons-edit.*schreibt.*dashicons-update.*überschreibt'
. '.*dashicons-trash.*löscht / Papierkorb#s',
$html
);
}
public function testHasASubmitButtonAboveAndBelowTheGroups(): void
{
$html = $this->render([]);
@@ -380,14 +380,14 @@ final class DiviTabTest extends TestCase
$html = $this->renderedWith(self::mixedStatus(), null, $overview);
self::assertStringContainsString('<th scope="col">Betroffene Werkzeuge</th>', $html);
self::assertStringContainsString('<th scope="col">Betroffene Tools</th>', $html);
self::assertStringContainsString(
'<td>Seiten schreiben</td><td><strong>inkompatibel</strong></td><td>–</td>',
'<td data-label="Fähigkeit">Seiten schreiben</td><td data-label="Zustand"><span class="lit-mcp-state lit-mcp-status-dismiss"><span class="dashicons dashicons-dismiss" aria-hidden="true"></span> <strong>inkompatibel</strong></span></td><td data-label="Quelle">–</td>',
$html
);
self::assertStringContainsString('<td>Modulkatalog</td><td><strong>ungetestet</strong></td>', $html);
self::assertStringContainsString('<td data-label="Fähigkeit">Modulkatalog</td><td data-label="Zustand"><span class="lit-mcp-state lit-mcp-status-warning"><span class="dashicons dashicons-warning" aria-hidden="true"></span> <strong>ungetestet</strong></span></td>', $html);
self::assertStringContainsString(
'<td>Seiten lesen</td><td><strong>freigegeben</strong></td><td>Automatik (auto)</td>',
'<td data-label="Fähigkeit">Seiten lesen</td><td data-label="Zustand"><span class="lit-mcp-state lit-mcp-status-yes-alt"><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span> <strong>freigegeben</strong></span></td><td data-label="Quelle">Automatisch geprüft</td>',
$html
);
self::assertStringContainsString('<li><code>lit-divi-insert</code></li>', $html);
@@ -412,8 +412,8 @@ final class DiviTabTest extends TestCase
self::report()
);
self::assertStringContainsString('Plugin (code)', $code);
self::assertStringContainsString('Freigabe auf dieser Website (database)', $database);
self::assertStringContainsString('Im Plugin als getestet hinterlegt', $code);
self::assertStringContainsString('Von dir auf dieser Website freigegeben', $database);
}
public function testTheTabExplainsTheAutomationAndTheFilter(): void
@@ -538,4 +538,23 @@ final class DiviTabTest extends TestCase
$tab->registerActions();
}
public function testNamesTheNextStepAboveTheDetailsAndKeepsAReleasedTableFolded(): void
{
$incompatible = $this->rendered(self::mixedStatus(), null);
$released = $this->rendered(ReleaseMatrices::released('5.3.0'), null);
self::assertStringContainsString('notice notice-error inline', $incompatible);
self::assertStringContainsString('<details class="lit-mcp-divi-details" open>', $incompatible);
self::assertStringNotContainsString('notice notice-', $released);
self::assertStringContainsString('<details class="lit-mcp-divi-details">', $released);
}
public function testUntestedVersionNamesTheCheckAsTheNextStep(): void
{
$html = $this->rendered(ReleaseMatrices::uniform(ReleaseState::Untested), null);
self::assertStringContainsString('notice notice-warning inline', $html);
self::assertStringContainsString('Divi 5.3.0 ist noch nicht geprüft. Starte unten die Prüfung', $html);
}
}