feat(admin): Reiter „Kompatibilität“ übersetzt und übersichtlicher
Sichtbarer Name „Kompatibilität“ (Slug compatibility bleibt) in Reiter, Hinweisen, Hilfe-Tab, CLI-Hilfe, Guides und readme. Oben eine Übersicht aller Plugins mit Gesamtzustand, Version, Zustand (Wort und Icon), Freigabe-Art in Klartext und Sprunglink; je Plugin erst Zustand und nächster Schritt, dann Aktionen, dann aufklappbare Details (offen bei Handlungsbedarf, ohne JavaScript). Tabellen mit Beschriftung und Kartenansicht bis 375 px, Buttons mit Plugin-Namen im zugänglichen Namen, Kontraste mit Reserve. Doku: Divi leert beim Prüfen den Cache einmal komplett. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
661d3945fd
commit
f337a57f26
@@ -157,6 +157,14 @@ final class CompatibilityTabTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/** The part of $html from the first $from to the next $to (the markers included). */
|
||||
private static function between(string $html, string $from, string $to): string
|
||||
{
|
||||
$start = (int) strpos($html, $from);
|
||||
|
||||
return substr($html, $start, (int) strpos($html, $to, $start) - $start + strlen($to));
|
||||
}
|
||||
|
||||
private static function html(CompatibilityTab $tab): string
|
||||
{
|
||||
ob_start();
|
||||
@@ -181,7 +189,166 @@ final class CompatibilityTabTest extends TestCase
|
||||
|
||||
self::assertInstanceOf(AdminTab::class, $tab);
|
||||
self::assertSame('compatibility', $tab->slug());
|
||||
self::assertSame('Compatibility', $tab->label());
|
||||
self::assertSame('Kompatibilität', $tab->label());
|
||||
}
|
||||
|
||||
/** @param list<Profile> $profiles */
|
||||
private function tabOver(array $profiles): CompatibilityTab
|
||||
{
|
||||
$reports = Mockery::mock(CheckReportStore::class);
|
||||
$reports->allows('latest')->andReturn(null);
|
||||
$store = Mockery::mock(ReleaseStore::class);
|
||||
$store->allows('entries')->andReturn([]);
|
||||
$store->allows('find')->andReturn(null);
|
||||
|
||||
return new CompatibilityTab(
|
||||
new Compatibility($profiles),
|
||||
FakeChangeLog::forbidden(),
|
||||
new AdminPostAction(static function (): void {
|
||||
}),
|
||||
static fn (): CheckReportStore => $reports,
|
||||
static fn (): ReleaseStore => $store
|
||||
);
|
||||
}
|
||||
|
||||
/** A second profile; $capability keeps the capability IDs unique across profiles. */
|
||||
private static function other(
|
||||
string $id,
|
||||
bool $active,
|
||||
Tier $tier = Tier::LockOnFailure,
|
||||
FakeCapability $capability = FakeCapability::Write
|
||||
): Profile {
|
||||
return new Profile(
|
||||
id: $id,
|
||||
label: ucfirst($id),
|
||||
tier: $tier,
|
||||
capabilities: [$capability],
|
||||
active: static fn (): bool => $active,
|
||||
version: static fn (): ?string => '2.0.0',
|
||||
matrix: $tier === Tier::ReleasePerVersion
|
||||
? static fn (): CompatibilityMatrix => CompatibilityMatrix::uniform([$capability], ReleaseState::Released, [], null)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
public function testTheOverviewListsEveryProfileAndLinksToTheSectionsOfTheActiveOnes(): void
|
||||
{
|
||||
$html = self::html($this->tabOver([
|
||||
self::other('alpha', true),
|
||||
self::other('beta', false, capability: FakeCapability::Other),
|
||||
self::other('gamma', true, capability: FakeCapability::Read),
|
||||
]));
|
||||
|
||||
self::assertStringContainsString('<h2>Übersicht</h2>', $html);
|
||||
foreach (['Alpha', 'Beta', 'Gamma'] as $label) {
|
||||
self::assertStringContainsString('data-label="Plugin">' . $label . '</th>', $html);
|
||||
}
|
||||
self::assertStringContainsString('href="#lit-mcp-compat-alpha"', $html);
|
||||
self::assertStringContainsString('href="#lit-mcp-compat-gamma"', $html);
|
||||
self::assertStringNotContainsString('#lit-mcp-compat-beta', $html);
|
||||
self::assertStringContainsString('id="lit-mcp-compat-alpha"', $html);
|
||||
self::assertStringNotContainsString('id="lit-mcp-compat-beta"', $html);
|
||||
self::assertSame(1, substr_count($html, '<strong>nicht aktiv</strong>'));
|
||||
// A link target in the page for every link, and the link names its plugin after the visible word.
|
||||
self::assertStringContainsString('>Details<span class="screen-reader-text"> zu Alpha</span></a>', $html);
|
||||
}
|
||||
|
||||
public function testTheOverviewSaysAtOnceWhetherEverythingIsFine(): void
|
||||
{
|
||||
$fine = self::html($this->tabOver([self::other('alpha', true)]));
|
||||
$attention = self::html($this->tabOver([
|
||||
self::profile(Tier::ReleasePerVersion, self::matrix(ReleaseState::Untested)),
|
||||
]));
|
||||
|
||||
self::assertStringContainsString('notice-success inline', $fine);
|
||||
self::assertStringContainsString('Alles in Ordnung', $fine);
|
||||
self::assertStringContainsString('notice-warning inline', $attention);
|
||||
self::assertStringContainsString('1 aktives Plugin braucht deine Aufmerksamkeit.', $attention);
|
||||
}
|
||||
|
||||
public function testTheOverviewTableIsAccessibleAndResponsive(): void
|
||||
{
|
||||
$html = self::html($this->tabOver([self::other('alpha', true)]));
|
||||
$table = self::between($html, 'lit-mcp-compat-overview', '</tbody></table>');
|
||||
|
||||
self::assertStringContainsString('<caption class="screen-reader-text">', $table);
|
||||
self::assertSame(5, substr_count($table, '<th scope="col">'));
|
||||
self::assertStringContainsString('<th scope="row" data-label="Plugin">Alpha</th>', $table);
|
||||
foreach (['Version', 'Zustand', 'Freigabe', 'Details'] as $label) {
|
||||
self::assertStringContainsString('<td data-label="' . $label . '">', $table);
|
||||
}
|
||||
self::assertStringContainsString('Sperrt automatisch bei Problemen', $table);
|
||||
}
|
||||
|
||||
public function testTierNamesNeverReachTheScreen(): void
|
||||
{
|
||||
foreach ([Tier::ReleasePerVersion, Tier::LockOnFailure, Tier::ReportOnly] as $tier) {
|
||||
$html = self::html($this->tabOver([self::other('alpha', true, $tier)]));
|
||||
|
||||
foreach (['LockOnFailure', 'ReleasePerVersion', 'ReportOnly'] as $raw) {
|
||||
self::assertStringNotContainsString($raw, $html);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testASectionShowsStateThenActionsThenDetails(): void
|
||||
{
|
||||
$html = self::html($this->tab(
|
||||
self::profile(Tier::ReleasePerVersion, self::matrix(ReleaseState::Untested)),
|
||||
self::report()
|
||||
));
|
||||
$section = substr($html, (int) strpos($html, '<section'));
|
||||
|
||||
self::assertStringContainsString('aria-labelledby="lit-mcp-compat-fake-title"', $section);
|
||||
self::assertStringContainsString('<h2 id="lit-mcp-compat-fake-title">Fake <Plugin></h2>', $section);
|
||||
$order = array_map(
|
||||
static fn (string $needle): int => (int) strpos($section, $needle),
|
||||
['notice notice-warning inline', '<h3>Aktionen</h3>', '<details', '<h3>Fähigkeiten</h3>', '<h3>Versionen</h3>', '<h3>Letzte Prüfung</h3>']
|
||||
);
|
||||
$sorted = $order;
|
||||
sort($sorted);
|
||||
self::assertSame($sorted, $order);
|
||||
self::assertNotContains(0, $order);
|
||||
}
|
||||
|
||||
public function testStateAndNextStepAreWordsAndNeverOnlyColour(): void
|
||||
{
|
||||
foreach (self::states() as [$state, $word]) {
|
||||
$html = self::html($this->tab(self::profile(Tier::ReleasePerVersion, self::matrix($state))));
|
||||
|
||||
// overview row, section notice and one row per capability: the word beside an icon hidden from AT
|
||||
$expected = 2 + count(self::CAPS);
|
||||
self::assertSame($expected, substr_count($html, '<strong>' . $word . '</strong>'));
|
||||
self::assertSame($expected, substr_count($html, 'aria-hidden="true"></span> <strong>' . $word . '</strong>'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testTheFeatureTableNamesEachRowByItsCapability(): void
|
||||
{
|
||||
$html = self::html($this->tab(self::profile()));
|
||||
|
||||
self::assertStringContainsString('<th scope="row" data-label="Fähigkeit">Fake fake-read</th>', $html);
|
||||
self::assertStringContainsString('<td data-label="Zustand">', $html);
|
||||
}
|
||||
|
||||
public function testTheDetailsSummaryNamesThePlugin(): void
|
||||
{
|
||||
$html = self::html($this->tab(self::profile()));
|
||||
|
||||
self::assertStringContainsString(
|
||||
'<summary>Details<span class="screen-reader-text"> zu Fake <Plugin></span></summary>',
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
public function testProblemsStandInTheStateNoticeEscaped(): void
|
||||
{
|
||||
$html = self::html($this->tab(
|
||||
self::profile(Tier::ReleasePerVersion, self::matrix(ReleaseState::Incompatible, ['Problem <A>']))
|
||||
));
|
||||
|
||||
self::assertStringContainsString('notice notice-error inline', $html);
|
||||
self::assertStringContainsString('<li>Problem <A></li>', $html);
|
||||
}
|
||||
|
||||
public function testAnInactiveProfileIsOneLineAndNeverAsksForTheMatrix(): void
|
||||
@@ -190,10 +357,13 @@ final class CompatibilityTabTest extends TestCase
|
||||
// ReleasePerVersion with a matrix closure that fails the test when called.
|
||||
$html = self::html($this->tab(self::profile(Tier::ReleasePerVersion, null, active: false)));
|
||||
|
||||
self::assertStringContainsString('nicht installiert oder nicht aktiv', $html);
|
||||
self::assertStringContainsString('Fake <Plugin>', $html);
|
||||
self::assertStringContainsString('<strong>nicht aktiv</strong>', $html);
|
||||
self::assertStringContainsString('data-label="Plugin">Fake <Plugin></th>', $html);
|
||||
self::assertStringContainsString('Kein unterstütztes Plugin ist aktiv', $html);
|
||||
self::assertStringNotContainsString('<section', $html);
|
||||
self::assertStringNotContainsString('<form', $html);
|
||||
self::assertStringNotContainsString('<table', $html);
|
||||
self::assertStringNotContainsString('href="#lit-mcp-compat-fake"', $html);
|
||||
self::assertSame(1, substr_count($html, '<table'));
|
||||
}
|
||||
|
||||
/** @dataProvider states */
|
||||
@@ -243,11 +413,8 @@ final class CompatibilityTabTest extends TestCase
|
||||
{
|
||||
return [
|
||||
'release' => [Tier::ReleasePerVersion, 'Freigabe pro Version nötig'],
|
||||
'lock' => [
|
||||
Tier::LockOnFailure,
|
||||
'Freigabe wird dokumentiert, Nutzung automatisch, solange die Prüfung grün ist',
|
||||
],
|
||||
'report' => [Tier::ReportOnly, 'nur Anzeige'],
|
||||
'lock' => [Tier::LockOnFailure, 'Sperrt automatisch bei Problemen'],
|
||||
'report' => [Tier::ReportOnly, 'Nur Anzeige'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -289,8 +456,8 @@ final class CompatibilityTabTest extends TestCase
|
||||
{
|
||||
$html = self::html($this->tab(self::profile()));
|
||||
|
||||
self::assertStringContainsString('Getestete Versionen', $html);
|
||||
self::assertStringContainsString('Freigegebene Versionen', $html);
|
||||
self::assertStringContainsString('<th scope="row">Im Plugin getestet</th>', $html);
|
||||
self::assertStringContainsString('<th scope="row">Von dir freigegeben</th>', $html);
|
||||
self::assertSame(2, substr_count($html, '<td>–</td>'));
|
||||
}
|
||||
|
||||
@@ -492,7 +659,7 @@ final class CompatibilityTabTest extends TestCase
|
||||
$html = self::html($this->tab(self::profile(), self::report()));
|
||||
|
||||
self::assertLessThan(strpos($html, 'lit-mcp-compat-details'), strpos($html, '[INTRO]'));
|
||||
self::assertGreaterThan(strpos($html, '</table></details>'), strpos($html, '[EXTRA]'));
|
||||
self::assertGreaterThan(strpos($html, '</details>'), strpos($html, '[EXTRA]'));
|
||||
}
|
||||
|
||||
public function testAnInactiveProfileFiresNeitherIntroNorExtra(): void
|
||||
@@ -542,7 +709,7 @@ final class CompatibilityTabTest extends TestCase
|
||||
$html = self::html($this->tab(self::profile()));
|
||||
|
||||
self::assertStringContainsString('<th scope="col">Gut</th>', $html);
|
||||
self::assertSame(3, substr_count($html, '<th scope="col">'));
|
||||
self::assertSame(3, substr_count(self::between($html, 'lit-mcp-compat-features', '</thead>'), '<th scope="col">'));
|
||||
self::assertStringContainsString('<td data-label="Gut"><ul><li>ok</li></ul></td>', $html);
|
||||
}
|
||||
|
||||
@@ -567,7 +734,7 @@ final class CompatibilityTabTest extends TestCase
|
||||
|
||||
self::assertStringContainsString('<td data-label="Kaputt">–</td>', $html);
|
||||
self::assertStringContainsString('<td data-label="Heil"><ul><li>ok</li></ul></td>', $html);
|
||||
self::assertStringContainsString('</tbody></table></details>', $html);
|
||||
self::assertStringContainsString('</tbody></table><h3>Versionen</h3>', $html);
|
||||
self::assertStringContainsString('fake capability column "Kaputt" failed', $logged);
|
||||
self::assertStringContainsString('Zelle <kaputt>', $logged);
|
||||
}
|
||||
@@ -591,7 +758,10 @@ final class CompatibilityTabTest extends TestCase
|
||||
{
|
||||
Filters\expectApplied(CompatibilityTab::COLUMNS_FILTER)->once()->andReturn('kaputt');
|
||||
|
||||
self::assertSame(2, substr_count(self::html($this->tab(self::profile())), '<th scope="col">'));
|
||||
self::assertSame(
|
||||
2,
|
||||
substr_count(self::between(self::html($this->tab(self::profile())), 'lit-mcp-compat-features', '</thead>'), '<th scope="col">')
|
||||
);
|
||||
}
|
||||
|
||||
public function testEverythingFromThirdPartiesIsEscaped(): void
|
||||
|
||||
@@ -278,7 +278,7 @@ final class SettingsPageTest extends TestCase
|
||||
static fn (array $tab): bool => $tab['id'] === 'lit-mcp-overview'
|
||||
&& str_contains($tab['content'], 'Tab „Verbindung“')
|
||||
&& str_contains($tab['content'], 'Tab „Tools“')
|
||||
&& str_contains($tab['content'], 'Tab „Compatibility“')
|
||||
&& str_contains($tab['content'], 'Tab „Kompatibilität“')
|
||||
));
|
||||
|
||||
(new SettingsPage([$this->tab('verbindung', 'Verbindung')]))->helpTabFor($screen);
|
||||
|
||||
Reference in New Issue
Block a user