refactor(divi): DiviTab geht im Reiter „Compatibility“ auf

Der eigene Divi-Reiter entfällt. DiviAdminExtras behält Admin-Hinweis, Orphan-Bereinigung vor der Ausgabe (lit_mcp_compat_profile_load), Hinweis zum nächsten Schritt und zur privaten Testseite (lit_mcp_compat_profile_intro), Zustandstexte mit Quelle (lit_mcp_compat_explanation), Automatik-Notiz und Visual-Builder-Hinweis (lit_mcp_compat_profile_extra) sowie die Spalten Quelle, Probleme und Betroffene Tools über die neue, gegen fehlerhafte Callbacks abgesicherte Erweiterung lit_mcp_compat_capability_columns. Texte, Doku, CSS (lit-mcp-compat-*) und Smoke auf den neuen Reiter umgestellt, tote divi_*-Hinweise entfernt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-06 13:32:29 +01:00
co-authored by Claude Opus 5.5
parent 85f4138b36
commit b40b81f585
23 changed files with 1020 additions and 1284 deletions
+154
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Admin;
use Brain\Monkey\Actions;
use Brain\Monkey\Filters;
use Brain\Monkey\Functions;
use Closure;
use LIT\MCP\Admin\AdminPostAction;
@@ -312,6 +313,45 @@ final class CompatibilityTabTest extends TestCase
);
}
public function testAProfileCanAddColumnsToTheCapabilityTableAndTheTabEscapesThem(): void
{
Filters\expectApplied(CompatibilityTab::COLUMNS_FILTER)->once()->with([], 'fake')->andReturn([
[
'heading' => 'Quelle <b>',
'cell' => static fn (FakeCapability $c, CompatibilityMatrix $m): array => $c === FakeCapability::Read
? ['<i>a</i>']
: [],
],
[
'heading' => 'Tools',
'cell' => static fn (FakeCapability $c, CompatibilityMatrix $m): array => ['tool-' . $c->id()],
'code' => true,
],
]);
$html = self::html($this->tab(self::profile()));
self::assertStringContainsString('<th scope="col">Quelle &lt;b&gt;</th>', $html);
self::assertStringContainsString('<td data-label="Quelle &lt;b&gt;"><ul><li>&lt;i&gt;a&lt;/i&gt;</li></ul></td>', $html);
self::assertStringContainsString('<td data-label="Quelle &lt;b&gt;">–</td>', $html);
self::assertStringContainsString('<li><code>tool-' . FakeCapability::Read->id() . '</code></li>', $html);
self::assertStringNotContainsString('<i>a</i>', $html);
}
public function testTheCapabilityTableIsOpenUnlessReleasedAndFolded(): void
{
$released = self::html($this->tab(
self::profile(Tier::ReleasePerVersion, self::matrix(ReleaseState::Released))
));
$untested = self::html($this->tab(
self::profile(Tier::ReleasePerVersion, self::matrix(ReleaseState::Untested))
));
self::assertStringContainsString('<details class="lit-mcp-compat-details">', $released);
self::assertStringContainsString('<details class="lit-mcp-compat-details" open>', $untested);
self::assertStringContainsString('lit-mcp-compat-features', $released);
}
public function testEveryButtonNamesItsProfileForAssistiveTechnology(): void
{
$profile = self::profile(Tier::ReleasePerVersion, self::matrix(ReleaseState::Untested));
@@ -430,6 +470,120 @@ final class CompatibilityTabTest extends TestCase
self::html($this->tab(self::profile(), self::report()));
}
public function testTheProfileIntroRunsBeforeTheCapabilityTableAndTheExtraAfterIt(): void
{
Actions\expectDone(CompatibilityTab::INTRO_ACTION)->once()->with('fake')->whenHappen(static function (): void {
echo '[INTRO]';
});
Actions\expectDone(CompatibilityTab::EXTRA_ACTION)->once()->with('fake')->whenHappen(static function (): void {
echo '[EXTRA]';
});
$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]'));
}
public function testAnInactiveProfileFiresNeitherIntroNorExtra(): void
{
Actions\expectDone(CompatibilityTab::INTRO_ACTION)->never();
Actions\expectDone(CompatibilityTab::EXTRA_ACTION)->never();
self::html($this->tab(self::profile(active: false)));
}
public function testAProfileCanReplaceTheGenericExplanationAndTheTabEscapesIt(): void
{
Filters\expectApplied(CompatibilityTab::EXPLANATION_FILTER)
->once()
->with('Alle Fähigkeiten sind nutzbar.', 'fake', ReleaseState::Released)
->andReturn('Eigener <b>Text</b>');
$html = self::html($this->tab(self::profile()));
self::assertStringContainsString('Eigener &lt;b&gt;Text&lt;/b&gt;', $html);
self::assertStringNotContainsString('Alle Fähigkeiten sind nutzbar.', $html);
}
public function testANonStringExplanationFromTheFilterKeepsTheGenericText(): void
{
Filters\expectApplied(CompatibilityTab::EXPLANATION_FILTER)->once()->andReturn(['x']);
self::assertStringContainsString('Alle Fähigkeiten sind nutzbar.', self::html($this->tab(self::profile())));
}
/** @return array{heading: string, cell: Closure} */
private static function column(string $heading, Closure $cell): array
{
return ['heading' => $heading, 'cell' => $cell];
}
public function testInvalidColumnsAreDroppedAndTheRestIsReindexed(): void
{
Filters\expectApplied(CompatibilityTab::COLUMNS_FILTER)->once()->andReturn([
3 => self::column('Gut', static fn (): array => ['ok']),
5 => ['heading' => 42, 'cell' => static fn (): array => ['x']],
6 => ['heading' => 'Ohne Closure', 'cell' => 'strlen'],
7 => 'kaputt',
9 => ['heading' => 'Ohne Zelle'],
]);
$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::assertStringContainsString('<td data-label="Gut"><ul><li>ok</li></ul></td>', $html);
}
public function testAThrowingCellShowsADashLogsTheFailureAndKeepsTheTableClosed(): void
{
Filters\expectApplied(CompatibilityTab::COLUMNS_FILTER)->once()->andReturn([
self::column('Kaputt', static function (): array {
throw new \RuntimeException('Zelle <kaputt>');
}),
self::column('Heil', static fn (): array => ['ok']),
]);
$log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
$previous = ini_set('error_log', $log);
try {
$html = self::html($this->tab(self::profile()));
} finally {
ini_set('error_log', (string) $previous);
}
$logged = (string) file_get_contents($log);
unlink($log);
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('fake capability column "Kaputt" failed', $logged);
self::assertStringContainsString('Zelle <kaputt>', $logged);
}
public function testACellThatReturnsNoListShowsADash(): void
{
Filters\expectApplied(CompatibilityTab::COLUMNS_FILTER)->once()->andReturn([
self::column('Text', static fn (): string => 'kein Array'),
self::column('Karte', static fn (): array => ['a' => 'x']),
self::column('Zahlen', static fn (): array => [1, 2]),
]);
$html = self::html($this->tab(self::profile()));
self::assertStringContainsString('<td data-label="Text">–</td>', $html);
self::assertStringContainsString('<td data-label="Karte">–</td>', $html);
self::assertStringContainsString('<td data-label="Zahlen">–</td>', $html);
}
public function testANonArrayColumnsFilterResultMeansNoExtraColumns(): void
{
Filters\expectApplied(CompatibilityTab::COLUMNS_FILTER)->once()->andReturn('kaputt');
self::assertSame(2, substr_count(self::html($this->tab(self::profile())), '<th scope="col">'));
}
public function testEverythingFromThirdPartiesIsEscaped(): void
{
$profile = self::profile(