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
+83
-9
@@ -132,7 +132,43 @@
|
|||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab "Compatibility": the report is a plain list, the icon replaces the bullet. */
|
/* Tab "Kompatibilität": one white section per plugin under the overview table. */
|
||||||
|
.lit-mcp-compat {
|
||||||
|
/* Jump links from the overview: keep the heading clear of the fixed admin bar. */
|
||||||
|
scroll-margin-top: 56px;
|
||||||
|
margin: 24px 0;
|
||||||
|
padding: 4px 20px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #c3c4c7;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat h3 {
|
||||||
|
margin: 1.2em 0 0.5em;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat-overview {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat .notice.inline ul {
|
||||||
|
margin: 0 0 0.5em 2em;
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat-actions form {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The report is a plain list, the icon replaces the bullet. */
|
||||||
.lit-mcp-compat-report {
|
.lit-mcp-compat-report {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -144,7 +180,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lit-mcp-status-yes-alt .dashicons {
|
.lit-mcp-status-yes-alt .dashicons {
|
||||||
color: #008a20;
|
color: #007017;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lit-mcp-status-warning .dashicons {
|
.lit-mcp-status-warning .dashicons {
|
||||||
@@ -155,6 +191,10 @@
|
|||||||
color: #d63638;
|
color: #d63638;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lit-mcp-status-minus .dashicons {
|
||||||
|
color: #50575e;
|
||||||
|
}
|
||||||
|
|
||||||
/* Folded help ("Wo finde ich das?") and the Divi detail table. */
|
/* Folded help ("Wo finde ich das?") and the Divi detail table. */
|
||||||
.lit-mcp-hint,
|
.lit-mcp-hint,
|
||||||
.lit-mcp-compat-details {
|
.lit-mcp-compat-details {
|
||||||
@@ -176,6 +216,12 @@
|
|||||||
margin: 1em 0 0.5em;
|
margin: 1em 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat-details summary:focus-visible,
|
||||||
|
.lit-mcp-hint summary:focus-visible {
|
||||||
|
outline: 2px solid #2271b1;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Only on phones: text that replaces a hidden column. */
|
/* Only on phones: text that replaces a hidden column. */
|
||||||
.lit-mcp-phone-only {
|
.lit-mcp-phone-only {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -435,9 +481,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab "Compatibility": on phones each row of the feature table becomes a card named by data-label. */
|
/* Tab "Kompatibilität": on phones each row of the overview and feature tables becomes a card named by data-label. */
|
||||||
@media screen and (max-width: 782px) {
|
@media screen and (max-width: 782px) {
|
||||||
.lit-mcp-compat-features thead {
|
.lit-mcp-compat {
|
||||||
|
padding: 4px 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat-features thead,
|
||||||
|
.lit-mcp-compat-overview thead {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
@@ -446,17 +497,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lit-mcp-compat-features tr,
|
.lit-mcp-compat-features tr,
|
||||||
.lit-mcp-compat-features td {
|
.lit-mcp-compat-features th,
|
||||||
|
.lit-mcp-compat-features td,
|
||||||
|
.lit-mcp-compat-overview tr,
|
||||||
|
.lit-mcp-compat-overview th,
|
||||||
|
.lit-mcp-compat-overview td {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* One card per row: a rule and room between them, the feature name first and bold. */
|
/* One card per row: a rule and room between them, the name first and bold. */
|
||||||
.lit-mcp-compat-features tr {
|
.lit-mcp-compat-features tr,
|
||||||
|
.lit-mcp-compat-overview tr {
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
border-bottom: 1px solid #c3c4c7;
|
border-bottom: 1px solid #c3c4c7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lit-mcp-compat-features td:first-child {
|
.lit-mcp-compat-features th:first-child,
|
||||||
|
.lit-mcp-compat-overview th:first-child {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +521,10 @@
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lit-mcp-compat-features td::before {
|
.lit-mcp-compat-features th::before,
|
||||||
|
.lit-mcp-compat-features td::before,
|
||||||
|
.lit-mcp-compat-overview th::before,
|
||||||
|
.lit-mcp-compat-overview td::before {
|
||||||
content: attr(data-label) ": ";
|
content: attr(data-label) ": ";
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -472,6 +532,16 @@
|
|||||||
.lit-mcp-compat-features ul {
|
.lit-mcp-compat-features ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Touch targets: the link in the overview and the check/release buttons. */
|
||||||
|
.lit-mcp-compat-overview a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat-actions .button {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab "Protokoll": filters in their own block above the table (core hides ".tablenav .actions"
|
/* Tab "Protokoll": filters in their own block above the table (core hides ".tablenav .actions"
|
||||||
@@ -567,3 +637,7 @@
|
|||||||
.lit-mcp-unit {
|
.lit-mcp-unit {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lit-mcp-compat-versions th {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,16 +93,17 @@ nichts geändert. Vorgehen:
|
|||||||
Werkzeuge (`tools`).
|
Werkzeuge (`tools`).
|
||||||
3. Ein anderes Werkzeug nutzen, das die betroffene Fähigkeit nicht braucht – ist z. B. nur „Seiten schreiben“
|
3. Ein anderes Werkzeug nutzen, das die betroffene Fähigkeit nicht braucht – ist z. B. nur „Seiten schreiben“
|
||||||
betroffen, funktioniert `lit-divi-outline` weiter, und der Nutzer kann die Änderung selbst im Divi-Builder machen.
|
betroffen, funktioniert `lit-divi-outline` weiter, und der Nutzer kann die Änderung selbst im Divi-Builder machen.
|
||||||
4. Den Nutzer informieren: bei `divi_untested` soll ein Administrator unter Einstellungen → LIT MCP → Compatibility die
|
4. Den Nutzer informieren: bei `divi_untested` soll ein Administrator unter Einstellungen → LIT MCP → Kompatibilität die
|
||||||
Prüfung ausführen und die Version freigeben; bei `divi_incompatible` muss LIT MCP aktualisiert werden. Die Sperre
|
Prüfung ausführen und die Version freigeben; bei `divi_incompatible` muss LIT MCP aktualisiert werden. Die Sperre
|
||||||
nicht über andere Werkzeuge umgehen.
|
nicht über andere Werkzeuge umgehen.
|
||||||
5. Dasselbe gilt für Pods: Ist eine Pods-Fähigkeit (`pods-…`) nicht nutzbar, antwortet das betroffene Werkzeug mit
|
5. Dasselbe gilt für Pods: Ist eine Pods-Fähigkeit (`pods-…`) nicht nutzbar, antwortet das betroffene Werkzeug mit
|
||||||
`compat_incompatible`; der Stand steht im selben Reiter „Compatibility“.
|
`compat_incompatible`; der Stand steht im selben Reiter „Kompatibilität“.
|
||||||
|
|
||||||
Seitenweite Änderungen (Optionen, Menüs und Menü-Positionen, Medien, Divi-Design, Theme Builder, globale
|
Seitenweite Änderungen (Optionen, Menüs und Menü-Positionen, Medien, Divi-Design, Theme Builder, globale
|
||||||
Bibliotheksmodule) leeren den Cache von WP Super Cache, Änderungen an einem Beitrag nur dessen Seite. Dabei leert der
|
Bibliotheksmodule) leeren den Cache von WP Super Cache, Änderungen an einem Beitrag nur dessen Seite. Dabei leert der
|
||||||
Purge eines einzelnen Beitrags die Cache-Dateien unter dem Host, über den der MCP-Aufruf eingeht; bei verschiedenen
|
Purge eines einzelnen Beitrags die Cache-Dateien unter dem Host, über den der MCP-Aufruf eingeht; bei verschiedenen
|
||||||
Hosts (z. B. mit und ohne `www`) bleiben die Dateien des anderen Hosts womöglich ungeleert (der Komplett-Purge ist davon
|
Hosts (z. B. mit und ohne `www`) bleiben die Dateien des anderen Hosts womöglich ungeleert (der Komplett-Purge ist davon
|
||||||
nicht betroffen). Ist der Cache dafür gesperrt, bleibt er unverändert; der Zustand steht im Reiter „Compatibility“
|
nicht betroffen). Ist der Cache dafür gesperrt, bleibt er unverändert; der Zustand steht im Reiter „Kompatibilität“
|
||||||
und in `lit-site-status` (`wpsc`). Der Nachlauf-Purge, der auf ein gedrosseltes Leeren des ganzen Caches folgt, braucht WP-Cron (oder einen
|
und in `lit-site-status` (`wpsc`). Der Nachlauf-Purge, der auf ein gedrosseltes Leeren des ganzen Caches folgt, braucht
|
||||||
System-Cron, der `wp-cron.php` aufruft).
|
WP-Cron (oder einen System-Cron, der `wp-cron.php` aufruft). Auf Divi-Seiten leert jede Prüfung im Reiter den ganzen
|
||||||
|
Cache einmal: Divi selbst leert ihn beim Anlegen und Löschen der Testseite.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ insgesamt:
|
|||||||
2. Mit Werkzeugen weiterarbeiten, deren Fähigkeit `released` ist (z. B. Seiten lesen mit `lit-divi-outline`), und den
|
2. Mit Werkzeugen weiterarbeiten, deren Fähigkeit `released` ist (z. B. Seiten lesen mit `lit-divi-outline`), und den
|
||||||
gesperrten Teil auslassen.
|
gesperrten Teil auslassen.
|
||||||
3. Dem Nutzer sagen, welche Fähigkeit gesperrt ist: bei `untested` muss ein Administrator unter Einstellungen → LIT
|
3. Dem Nutzer sagen, welche Fähigkeit gesperrt ist: bei `untested` muss ein Administrator unter Einstellungen → LIT
|
||||||
MCP → Compatibility die Prüfung ausführen und freigeben, bei `incompatible` muss LIT MCP aktualisiert werden.
|
MCP → Kompatibilität die Prüfung ausführen und freigeben, bei `incompatible` muss LIT MCP aktualisiert werden.
|
||||||
|
|
||||||
## Bibliothek und globale Module
|
## Bibliothek und globale Module
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -205,7 +205,7 @@ Weg nicht: der Wechsel auf 1.0.2 ist einmalig von Hand (ZIP hochladen), danach l
|
|||||||
|
|
||||||
== Divi-Freigabe ==
|
== Divi-Freigabe ==
|
||||||
|
|
||||||
LIT MCP schreibt nur mit Divi-Versionen, die für diese Plugin-Version geprüft sind. Nach einem Divi-Update lesen die Divi-Werkzeuge weiter; schreibende Divi-Werkzeuge antworten mit „divi_untested“, bis ein Administrator unter Einstellungen → LIT MCP → Compatibility die Prüfung startet und die Version freigibt. Die Prüfung legt eine Entwurfsseite an, schreibt und rendert sie und löscht sie danach endgültig. Hat Divi Stellen geändert, die LIT MCP nachbildet, ist die Version „inkompatibel“ (divi_incompatible) – dann hilft nur ein Update von LIT MCP. Für Entwickler: `wp --user=<admin> lit-mcp divi-check [--diff] [--update-reference]`.
|
LIT MCP schreibt nur mit Divi-Versionen, die für diese Plugin-Version geprüft sind. Nach einem Divi-Update lesen die Divi-Werkzeuge weiter; schreibende Divi-Werkzeuge antworten mit „divi_untested“, bis ein Administrator unter Einstellungen → LIT MCP → Kompatibilität die Prüfung startet und die Version freigibt. Die Prüfung legt eine Entwurfsseite an, schreibt und rendert sie und löscht sie danach endgültig. Hat Divi Stellen geändert, die LIT MCP nachbildet, ist die Version „inkompatibel“ (divi_incompatible) – dann hilft nur ein Update von LIT MCP. Für Entwickler: `wp --user=<admin> lit-mcp divi-check [--diff] [--update-reference]`.
|
||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
|||||||
+216
-58
@@ -22,14 +22,16 @@ use Throwable;
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab "Compatibility": one section per third-party plugin of Compatibility::profiles() – its version, overall state
|
* Tab "Kompatibilität" (slug "compatibility"): an overview table of all third-party plugins of
|
||||||
* (word and icon, never colour alone), what the tier means for a release, the capabilities, tested and released
|
* Compatibility::profiles() (version, state as word and icon – never colour alone –, kind of release, link to the
|
||||||
* versions, the latest check report and "Prüfung starten" (Admin\CheckRoute). A profile that is not active is one
|
* section), then one section per active plugin: state and next step first, then the actions ("Prüfung starten",
|
||||||
* line and never asks for its matrix. Everything that comes from a third-party plugin (version, problem texts, step
|
* Admin\CheckRoute, release, withdrawal) and, folded, the details (capabilities, tested and released versions, the
|
||||||
* labels) is escaped on output. Profiles hook in: lit_mcp_compat_profile_load($id) runs before any output (see
|
* latest check report). A profile that is not active is one overview row and never asks for its matrix. Everything
|
||||||
* loadProfiles()), lit_mcp_compat_profile_intro($id) opens the section above the capability table (next step,
|
* that comes from a third-party plugin (version, problem texts, step labels) is escaped on output. Profiles hook in:
|
||||||
* notices), lit_mcp_compat_profile_extra($id) closes it, and the filter lit_mcp_compat_explanation($text, $id,
|
* lit_mcp_compat_profile_load($id) runs before any output (see loadProfiles()),
|
||||||
* ReleaseState) replaces the generic sentence next to the overall state.
|
* lit_mcp_compat_profile_intro($id) opens the section below the state notice (next step, notices),
|
||||||
|
* lit_mcp_compat_profile_extra($id) closes it, and the filter lit_mcp_compat_explanation($text, $id, ReleaseState)
|
||||||
|
* replaces the generic sentence next to the overall state.
|
||||||
*/
|
*/
|
||||||
final class CompatibilityTab implements AdminTab
|
final class CompatibilityTab implements AdminTab
|
||||||
{
|
{
|
||||||
@@ -94,7 +96,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
return __('Compatibility', 'wp-lit-mcp');
|
return __('Kompatibilität', 'wp-lit-mcp');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerSettings(): void
|
public function registerSettings(): void
|
||||||
@@ -227,71 +229,213 @@ final class CompatibilityTab implements AdminTab
|
|||||||
|
|
||||||
public function render(): void
|
public function render(): void
|
||||||
{
|
{
|
||||||
foreach ($this->compatibility->profiles() as $profile) {
|
$profiles = $this->compatibility->profiles();
|
||||||
$this->renderProfile($profile);
|
$this->renderOverview($profiles);
|
||||||
|
foreach ($profiles as $profile) {
|
||||||
|
if ($this->compatibility->active($profile)) {
|
||||||
|
$this->renderProfile($profile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderProfile(Profile $profile): void
|
/**
|
||||||
|
* The whole page at a glance: one sentence on whether anything needs attention, then one row per plugin. Rows of
|
||||||
|
* active plugins link to their section; an inactive plugin is one short row.
|
||||||
|
*
|
||||||
|
* @param list<Profile> $profiles
|
||||||
|
*/
|
||||||
|
private function renderOverview(array $profiles): void
|
||||||
{
|
{
|
||||||
printf('<section class="lit-mcp-compat" id="lit-mcp-compat-%s">', esc_attr($profile->id));
|
$active = array_filter($profiles, fn (Profile $profile): bool => $this->compatibility->active($profile));
|
||||||
printf('<h2>%s</h2>', esc_html($profile->label));
|
$attention = count(array_filter(
|
||||||
|
$active,
|
||||||
|
fn (Profile $profile): bool => $this->compatibility->matrix($profile)->state() !== ReleaseState::Released
|
||||||
|
));
|
||||||
|
|
||||||
|
echo '<p>' . esc_html__(
|
||||||
|
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||||
|
'Hier siehst du, ob LIT MCP zu deinen Plugins passt. Was nicht passt, sperrt LIT MCP; alles andere läuft weiter.',
|
||||||
|
'wp-lit-mcp'
|
||||||
|
) . '</p>';
|
||||||
|
echo '<h2>' . esc_html__('Übersicht', 'wp-lit-mcp') . '</h2>';
|
||||||
|
[$class, $text] = match (true) {
|
||||||
|
$active === [] => ['info', __('Kein unterstütztes Plugin ist aktiv. Es gibt nichts zu tun.', 'wp-lit-mcp')],
|
||||||
|
$attention === 0 => [
|
||||||
|
'success',
|
||||||
|
__('Alles in Ordnung: Alle aktiven Plugins sind freigegeben.', 'wp-lit-mcp'),
|
||||||
|
],
|
||||||
|
default => ['warning', sprintf(
|
||||||
|
/* translators: %d: number of plugins */
|
||||||
|
_n(
|
||||||
|
'%d aktives Plugin braucht deine Aufmerksamkeit.',
|
||||||
|
'%d aktive Plugins brauchen deine Aufmerksamkeit.',
|
||||||
|
$attention,
|
||||||
|
'wp-lit-mcp'
|
||||||
|
),
|
||||||
|
$attention
|
||||||
|
)],
|
||||||
|
};
|
||||||
|
printf('<div class="notice notice-%s inline"><p>%s</p></div>', esc_attr($class), esc_html($text));
|
||||||
|
|
||||||
|
$headings = [
|
||||||
|
__('Plugin', 'wp-lit-mcp'),
|
||||||
|
__('Version', 'wp-lit-mcp'),
|
||||||
|
__('Zustand', 'wp-lit-mcp'),
|
||||||
|
__('Freigabe', 'wp-lit-mcp'),
|
||||||
|
__('Details', 'wp-lit-mcp'),
|
||||||
|
];
|
||||||
|
echo '<table class="widefat striped lit-mcp-compat-overview"><caption class="screen-reader-text">'
|
||||||
|
. esc_html__('Alle unterstützten Plugins und ihr Zustand', 'wp-lit-mcp') . '</caption><thead><tr>';
|
||||||
|
foreach ($headings as $heading) {
|
||||||
|
echo '<th scope="col">' . esc_html($heading) . '</th>';
|
||||||
|
}
|
||||||
|
echo '</tr></thead><tbody>';
|
||||||
|
foreach ($profiles as $profile) {
|
||||||
|
$this->renderOverviewRow($profile, $headings);
|
||||||
|
}
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param list<string> $headings */
|
||||||
|
private function renderOverviewRow(Profile $profile, array $headings): void
|
||||||
|
{
|
||||||
|
printf(
|
||||||
|
'<tr><th scope="row" data-label="%s">%s</th>',
|
||||||
|
esc_attr($headings[0]),
|
||||||
|
esc_html($profile->label)
|
||||||
|
);
|
||||||
if (! $this->compatibility->active($profile)) {
|
if (! $this->compatibility->active($profile)) {
|
||||||
echo '<p>' . esc_html__('nicht installiert oder nicht aktiv', 'wp-lit-mcp') . '</p></section>';
|
$none = '<span aria-hidden="true">–</span><span class="screen-reader-text">'
|
||||||
|
. esc_html__('keine Angabe', 'wp-lit-mcp') . '</span>';
|
||||||
|
printf(
|
||||||
|
'<td data-label="%1$s">%5$s</td><td data-label="%2$s">%6$s</td><td data-label="%3$s">%5$s</td>'
|
||||||
|
. '<td data-label="%4$s">%5$s</td></tr>',
|
||||||
|
esc_attr($headings[1]),
|
||||||
|
esc_attr($headings[2]),
|
||||||
|
esc_attr($headings[3]),
|
||||||
|
esc_attr($headings[4]),
|
||||||
|
$none, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
|
||||||
|
'<span class="lit-mcp-state lit-mcp-status-minus"><span class="dashicons dashicons-minus"'
|
||||||
|
. ' aria-hidden="true"></span> <strong>' . esc_html__('nicht aktiv', 'wp-lit-mcp')
|
||||||
|
. '</strong></span>'
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<td data-label="%1$s">%5$s</td><td data-label="%2$s">%6$s</td><td data-label="%3$s">%7$s</td>'
|
||||||
|
. '<td data-label="%4$s"><a href="#lit-mcp-compat-%8$s">%9$s<span class="screen-reader-text">'
|
||||||
|
. ' %10$s</span></a></td></tr>',
|
||||||
|
esc_attr($headings[1]),
|
||||||
|
esc_attr($headings[2]),
|
||||||
|
esc_attr($headings[3]),
|
||||||
|
esc_attr($headings[4]),
|
||||||
|
esc_html($this->compatibility->version($profile) ?? __('nicht erkannt', 'wp-lit-mcp')),
|
||||||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in stateBadge().
|
||||||
|
self::stateBadge($this->compatibility->matrix($profile)->state()),
|
||||||
|
esc_html(self::tierHint($profile->tier)),
|
||||||
|
esc_attr($profile->id),
|
||||||
|
esc_html__('Details', 'wp-lit-mcp'),
|
||||||
|
esc_html(sprintf(
|
||||||
|
/* translators: %s: plugin name */
|
||||||
|
__('zu %s', 'wp-lit-mcp'),
|
||||||
|
$profile->label
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderProfile(Profile $profile): void
|
||||||
|
{
|
||||||
$matrix = $this->compatibility->matrix($profile);
|
$matrix = $this->compatibility->matrix($profile);
|
||||||
$problems = $this->compatibility->problems($profile);
|
$problems = $this->compatibility->problems($profile);
|
||||||
$state = $matrix->state();
|
$state = $matrix->state();
|
||||||
|
|
||||||
echo '<table class="form-table" role="presentation"><tbody>';
|
|
||||||
printf(
|
printf(
|
||||||
'<tr><th scope="row">%s</th><td>%s</td></tr>',
|
'<section class="lit-mcp-compat" id="lit-mcp-compat-%1$s" aria-labelledby="lit-mcp-compat-%1$s-title">',
|
||||||
esc_html__('Version', 'wp-lit-mcp'),
|
esc_attr($profile->id)
|
||||||
esc_html($this->compatibility->version($profile) ?? __('nicht erkannt', 'wp-lit-mcp'))
|
|
||||||
);
|
);
|
||||||
|
printf('<h2 id="lit-mcp-compat-%s-title">%s</h2>', esc_attr($profile->id), esc_html($profile->label));
|
||||||
|
|
||||||
|
// 1. State and next step.
|
||||||
printf(
|
printf(
|
||||||
'<tr><th scope="row">%s</th><td>%s – %s</td></tr>',
|
'<div class="notice notice-%s inline"><p>%s %s</p>',
|
||||||
esc_html__('Gesamtzustand', 'wp-lit-mcp'),
|
esc_attr(self::noticeClass($state)),
|
||||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in stateBadge().
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in stateBadge().
|
||||||
self::stateBadge($state),
|
self::stateBadge($state),
|
||||||
esc_html($this->explanation($profile, $state))
|
esc_html($this->explanation($profile, $state))
|
||||||
);
|
);
|
||||||
if ($problems !== []) {
|
if ($problems !== []) {
|
||||||
echo '<tr><th scope="row">' . esc_html__('Probleme', 'wp-lit-mcp') . '</th><td>';
|
|
||||||
self::renderList($problems);
|
self::renderList($problems);
|
||||||
echo '</td></tr>';
|
|
||||||
}
|
}
|
||||||
|
echo '</div>';
|
||||||
printf(
|
printf(
|
||||||
'<tr><th scope="row">%s</th><td>%s</td></tr>',
|
'<p class="description">%s</p>',
|
||||||
esc_html__('Freigabe', 'wp-lit-mcp'),
|
esc_html(sprintf(
|
||||||
esc_html(self::tierHint($profile->tier))
|
/* translators: 1: plugin version, 2: how releases work for this plugin */
|
||||||
|
__('Installiert: Version %1$s. %2$s', 'wp-lit-mcp'),
|
||||||
|
$this->compatibility->version($profile) ?? __('nicht erkannt', 'wp-lit-mcp'),
|
||||||
|
self::tierExplanation($profile->tier)
|
||||||
|
))
|
||||||
);
|
);
|
||||||
printf(
|
|
||||||
'<tr><th scope="row">%s</th><td>%s</td></tr>',
|
|
||||||
esc_html__('Getestete Versionen', 'wp-lit-mcp'),
|
|
||||||
$profile->testedVersions === [] ? '–' : esc_html(implode(', ', $profile->testedVersions))
|
|
||||||
);
|
|
||||||
printf(
|
|
||||||
'<tr><th scope="row">%s</th><td>%s</td></tr>',
|
|
||||||
esc_html__('Freigegebene Versionen', 'wp-lit-mcp'),
|
|
||||||
esc_html($this->releasedVersions($profile)) ?: '–'
|
|
||||||
);
|
|
||||||
echo '</tbody></table>';
|
|
||||||
|
|
||||||
do_action(self::INTRO_ACTION, $profile->id);
|
do_action(self::INTRO_ACTION, $profile->id);
|
||||||
$this->renderCapabilities($profile, $matrix);
|
|
||||||
|
// 2. Actions.
|
||||||
$report = ($this->reports)($profile)->latest();
|
$report = ($this->reports)($profile)->latest();
|
||||||
$this->renderForms($profile, $report);
|
$this->renderForms($profile, $report);
|
||||||
|
|
||||||
|
// 3. Details, folded while everything is fine.
|
||||||
|
printf(
|
||||||
|
'<details class="lit-mcp-compat-details"%s><summary>%s'
|
||||||
|
. '<span class="screen-reader-text"> %s</span></summary>',
|
||||||
|
$state === ReleaseState::Released ? '' : ' open',
|
||||||
|
esc_html__('Details', 'wp-lit-mcp'),
|
||||||
|
esc_html(sprintf(
|
||||||
|
/* translators: %s: plugin name */
|
||||||
|
__('zu %s', 'wp-lit-mcp'),
|
||||||
|
$profile->label
|
||||||
|
))
|
||||||
|
);
|
||||||
|
$this->renderCapabilities($profile, $matrix);
|
||||||
|
$this->renderVersions($profile);
|
||||||
if ($report !== null) {
|
if ($report !== null) {
|
||||||
$this->renderReport($profile, $report);
|
$this->renderReport($profile, $report);
|
||||||
}
|
}
|
||||||
|
echo '</details>';
|
||||||
do_action(self::EXTRA_ACTION, $profile->id);
|
do_action(self::EXTRA_ACTION, $profile->id);
|
||||||
echo '</section>';
|
echo '</section>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function noticeClass(ReleaseState $state): string
|
||||||
|
{
|
||||||
|
return match ($state) {
|
||||||
|
ReleaseState::Released => 'success',
|
||||||
|
ReleaseState::Untested => 'warning',
|
||||||
|
ReleaseState::Incompatible => 'error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderVersions(Profile $profile): void
|
||||||
|
{
|
||||||
|
$rows = [
|
||||||
|
__('Im Plugin getestet', 'wp-lit-mcp') => $profile->testedVersions === []
|
||||||
|
? '–' : esc_html(implode(', ', $profile->testedVersions)),
|
||||||
|
__('Von dir freigegeben', 'wp-lit-mcp') => esc_html($this->releasedVersions($profile)) ?: '–',
|
||||||
|
];
|
||||||
|
echo '<h3>' . esc_html__('Versionen', 'wp-lit-mcp') . '</h3>';
|
||||||
|
echo '<table class="widefat striped lit-mcp-compat-versions"><caption class="screen-reader-text">'
|
||||||
|
. esc_html(sprintf(
|
||||||
|
/* translators: %s: plugin name */
|
||||||
|
__('Getestete und freigegebene Versionen von %s', 'wp-lit-mcp'),
|
||||||
|
$profile->label
|
||||||
|
)) . '</caption><tbody>';
|
||||||
|
foreach ($rows as $heading => $value) {
|
||||||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $value is escaped above.
|
||||||
|
printf('<tr><th scope="row">%s</th><td>%s</td></tr>', esc_html($heading), $value);
|
||||||
|
}
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
private function renderCapabilities(Profile $profile, CompatibilityMatrix $matrix): void
|
private function renderCapabilities(Profile $profile, CompatibilityMatrix $matrix): void
|
||||||
{
|
{
|
||||||
$extra = $this->extraColumns($profile);
|
$extra = $this->extraColumns($profile);
|
||||||
@@ -300,12 +444,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
$headings[] = $column['heading'];
|
$headings[] = $column['heading'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open when something needs attention; a released profile keeps the long table folded.
|
echo '<h3>' . esc_html__('Fähigkeiten', 'wp-lit-mcp') . '</h3>';
|
||||||
printf(
|
|
||||||
'<details class="lit-mcp-compat-details"%s><summary>%s</summary>',
|
|
||||||
$matrix->state() === ReleaseState::Released ? '' : ' open',
|
|
||||||
esc_html__('Fähigkeiten im Detail', 'wp-lit-mcp')
|
|
||||||
);
|
|
||||||
echo '<table class="widefat striped lit-mcp-compat-features"><caption class="screen-reader-text">'
|
echo '<table class="widefat striped lit-mcp-compat-features"><caption class="screen-reader-text">'
|
||||||
. esc_html(sprintf(
|
. esc_html(sprintf(
|
||||||
/* translators: %s: plugin name */
|
/* translators: %s: plugin name */
|
||||||
@@ -320,7 +459,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
foreach ($profile->capabilities as $capability) {
|
foreach ($profile->capabilities as $capability) {
|
||||||
// data-label: on phones each row becomes a card and the cell names itself.
|
// data-label: on phones each row becomes a card and the cell names itself.
|
||||||
printf(
|
printf(
|
||||||
'<tr><td data-label="%s">%s</td><td data-label="%s">%s</td>',
|
'<tr><th scope="row" data-label="%s">%s</th><td data-label="%s">%s</td>',
|
||||||
esc_attr($headings[0]),
|
esc_attr($headings[0]),
|
||||||
esc_html($capability->label()),
|
esc_html($capability->label()),
|
||||||
esc_attr($headings[1]),
|
esc_attr($headings[1]),
|
||||||
@@ -337,7 +476,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
}
|
}
|
||||||
echo '</tr>';
|
echo '</tr>';
|
||||||
}
|
}
|
||||||
echo '</tbody></table></details>';
|
echo '</tbody></table>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return list<array{heading: string, cell: Closure, code?: bool}> */
|
/** @return list<array{heading: string, cell: Closure, code?: bool}> */
|
||||||
@@ -385,6 +524,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
$fresh = $report !== null && $report->freshFor($version, Plugin::VERSION, $this->stateKey($profile, $version));
|
$fresh = $report !== null && $report->freshFor($version, Plugin::VERSION, $this->stateKey($profile, $version));
|
||||||
$releasable = $this->releasable($profile, $version);
|
$releasable = $this->releasable($profile, $version);
|
||||||
|
|
||||||
|
echo '<h3>' . esc_html__('Aktionen', 'wp-lit-mcp') . '</h3><div class="lit-mcp-compat-actions">';
|
||||||
printf('<form method="post" action="%s">', esc_url(CheckRoute::url($profile->id)));
|
printf('<form method="post" action="%s">', esc_url(CheckRoute::url($profile->id)));
|
||||||
printf('<input type="hidden" name="_wpnonce" value="%s">', esc_attr(wp_create_nonce('wp_rest')));
|
printf('<input type="hidden" name="_wpnonce" value="%s">', esc_attr(wp_create_nonce('wp_rest')));
|
||||||
// The check is the one thing to do while a release is due without a usable report.
|
// The check is the one thing to do while a release is due without a usable report.
|
||||||
@@ -395,7 +535,6 @@ final class CompatibilityTab implements AdminTab
|
|||||||
false,
|
false,
|
||||||
['aria-label' => self::namedFor($profile, __('Prüfung starten', 'wp-lit-mcp'))]
|
['aria-label' => self::namedFor($profile, __('Prüfung starten', 'wp-lit-mcp'))]
|
||||||
);
|
);
|
||||||
echo '<p class="description">' . esc_html__('Die Prüfung dauert einige Sekunden.', 'wp-lit-mcp') . '</p>';
|
|
||||||
echo '</form>';
|
echo '</form>';
|
||||||
|
|
||||||
if ($releasable && $fresh) {
|
if ($releasable && $fresh) {
|
||||||
@@ -407,11 +546,6 @@ final class CompatibilityTab implements AdminTab
|
|||||||
sprintf(__('%1$s %2$s freigeben', 'wp-lit-mcp'), $profile->label, $version),
|
sprintf(__('%1$s %2$s freigeben', 'wp-lit-mcp'), $profile->label, $version),
|
||||||
'primary'
|
'primary'
|
||||||
);
|
);
|
||||||
} elseif ($releasable) {
|
|
||||||
echo '<p>' . esc_html__(
|
|
||||||
'Freigeben geht erst nach einer vollständig grünen Prüfung dieser Version – Prüfung erneut ausführen.',
|
|
||||||
'wp-lit-mcp'
|
|
||||||
) . '</p>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A release applies whatever the overall state: one incompatible capability must not hide the way back.
|
// A release applies whatever the overall state: one incompatible capability must not hide the way back.
|
||||||
@@ -425,6 +559,16 @@ final class CompatibilityTab implements AdminTab
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
echo '</div>';
|
||||||
|
|
||||||
|
echo '<p class="description">' . esc_html__('Die Prüfung dauert einige Sekunden.', 'wp-lit-mcp') . '</p>';
|
||||||
|
if ($releasable && ! $fresh) {
|
||||||
|
echo '<p>' . esc_html__(
|
||||||
|
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||||
|
'Freigeben geht erst nach einer vollständig grünen Prüfung dieser Version. Führe die Prüfung erneut aus.',
|
||||||
|
'wp-lit-mcp'
|
||||||
|
) . '</p>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** "<visible text>: <profile>" – the accessible name starts with the visible text (WCAG 2.5.3). */
|
/** "<visible text>: <profile>" – the accessible name starts with the visible text (WCAG 2.5.3). */
|
||||||
@@ -467,7 +611,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
esc_html__('Letzte Prüfung', 'wp-lit-mcp'),
|
esc_html__('Letzte Prüfung', 'wp-lit-mcp'),
|
||||||
esc_html(sprintf(
|
esc_html(sprintf(
|
||||||
/* translators: 1: plugin version, 2: LIT MCP version, 3: date */
|
/* translators: 1: plugin version, 2: LIT MCP version, 3: date */
|
||||||
__('Version %1$s mit LIT MCP %2$s, %3$s', 'wp-lit-mcp'),
|
__('Geprüft: Version %1$s mit LIT MCP %2$s, %3$s', 'wp-lit-mcp'),
|
||||||
$report->version,
|
$report->version,
|
||||||
$report->pluginVersion,
|
$report->pluginVersion,
|
||||||
date_i18n((string) get_option('date_format'), (int) strtotime($report->createdAt))
|
date_i18n((string) get_option('date_format'), (int) strtotime($report->createdAt))
|
||||||
@@ -556,7 +700,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
return match ($state) {
|
return match ($state) {
|
||||||
ReleaseState::Released => __('Alle Fähigkeiten sind nutzbar.', 'wp-lit-mcp'),
|
ReleaseState::Released => __('Alle Fähigkeiten sind nutzbar.', 'wp-lit-mcp'),
|
||||||
ReleaseState::Untested => __(
|
ReleaseState::Untested => __(
|
||||||
'Diese Version ist nicht geprüft. Die ungetesteten Fähigkeiten sind gesperrt, bis sie freigegeben ist.',
|
'Diese Version ist nicht geprüft. Die ungetesteten Fähigkeiten sind gesperrt, bis du sie freigibst.',
|
||||||
'wp-lit-mcp'
|
'wp-lit-mcp'
|
||||||
),
|
),
|
||||||
ReleaseState::Incompatible => __(
|
ReleaseState::Incompatible => __(
|
||||||
@@ -566,15 +710,29 @@ final class CompatibilityTab implements AdminTab
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Short and in plain words: the tier's name never reaches the screen. */
|
||||||
private static function tierHint(Tier $tier): string
|
private static function tierHint(Tier $tier): string
|
||||||
{
|
{
|
||||||
return match ($tier) {
|
return match ($tier) {
|
||||||
Tier::ReleasePerVersion => __('Freigabe pro Version nötig', 'wp-lit-mcp'),
|
Tier::ReleasePerVersion => __('Freigabe pro Version nötig', 'wp-lit-mcp'),
|
||||||
Tier::LockOnFailure => __(
|
Tier::LockOnFailure => __('Sperrt automatisch bei Problemen', 'wp-lit-mcp'),
|
||||||
'Freigabe wird dokumentiert, Nutzung automatisch, solange die Prüfung grün ist',
|
Tier::ReportOnly => __('Nur Anzeige', 'wp-lit-mcp'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function tierExplanation(Tier $tier): string
|
||||||
|
{
|
||||||
|
return match ($tier) {
|
||||||
|
Tier::ReleasePerVersion => __(
|
||||||
|
'Jede neue Version musst du einmal freigeben, bevor LIT MCP damit schreibt.',
|
||||||
'wp-lit-mcp'
|
'wp-lit-mcp'
|
||||||
),
|
),
|
||||||
Tier::ReportOnly => __('nur Anzeige', 'wp-lit-mcp'),
|
Tier::LockOnFailure => __(
|
||||||
|
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||||
|
'LIT MCP sperrt betroffene Fähigkeiten selbst, sobald die Prüfung Probleme findet. Eine Freigabe hält nur fest, dass du diese Version geprüft hast.',
|
||||||
|
'wp-lit-mcp'
|
||||||
|
),
|
||||||
|
Tier::ReportOnly => __('Probleme werden nur angezeigt; nichts wird gesperrt.', 'wp-lit-mcp'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ final class SettingsPage
|
|||||||
'wp-lit-mcp'
|
'wp-lit-mcp'
|
||||||
),
|
),
|
||||||
__('Passt es zu Divi, Pods & Co.?', 'wp-lit-mcp') => __(
|
__('Passt es zu Divi, Pods & Co.?', 'wp-lit-mcp') => __(
|
||||||
'Tab „Compatibility“: Fremd-Plugins prüfen, Versionen freigeben und sehen, was gesperrt ist.',
|
'Tab „Kompatibilität“: Fremd-Plugins prüfen, Versionen freigeben und sehen, was gesperrt ist.',
|
||||||
'wp-lit-mcp'
|
'wp-lit-mcp'
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ final class DiviAdminExtras
|
|||||||
))
|
))
|
||||||
)),
|
)),
|
||||||
esc_url(SettingsPage::url(CompatibilityTab::SLUG)),
|
esc_url(SettingsPage::url(CompatibilityTab::SLUG)),
|
||||||
esc_html__('Details im Reiter „Compatibility“', 'wp-lit-mcp')
|
esc_html__('Details im Reiter „Kompatibilität“', 'wp-lit-mcp')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +298,8 @@ final class DiviAdminExtras
|
|||||||
/** E1: what the automatic release does and how to switch it off. */
|
/** E1: what the automatic release does and how to switch it off. */
|
||||||
private function renderAutomationNote(): void
|
private function renderAutomationNote(): void
|
||||||
{
|
{
|
||||||
|
echo '<details class="lit-mcp-hint"><summary>'
|
||||||
|
. esc_html__('So funktioniert die automatische Freigabe', 'wp-lit-mcp') . '</summary>';
|
||||||
echo '<p class="description">' . esc_html__(
|
echo '<p class="description">' . esc_html__(
|
||||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||||
'Automatik: Ist die installierte Divi-Version im Plugin nicht als getestet hinterlegt, gibt LIT MCP jede Fähigkeit einzeln frei, wenn alle benutzten Divi-Funktionen vorhanden sind, ihre Aufrufe (Signaturen) und die Fingerabdrücke nachgebauter Divi-Abläufe unverändert sind und alle Konstanten definiert sind. Das prüft Aufrufformen, nicht das Verhalten von Divi. Fehlt für eine Stelle die Referenz, ist die Fähigkeit ungetestet; weicht etwas ab, ist sie inkompatibel.',
|
'Automatik: Ist die installierte Divi-Version im Plugin nicht als getestet hinterlegt, gibt LIT MCP jede Fähigkeit einzeln frei, wenn alle benutzten Divi-Funktionen vorhanden sind, ihre Aufrufe (Signaturen) und die Fingerabdrücke nachgebauter Divi-Abläufe unverändert sind und alle Konstanten definiert sind. Das prüft Aufrufformen, nicht das Verhalten von Divi. Fehlt für eine Stelle die Referenz, ist die Fähigkeit ungetestet; weicht etwas ab, ist sie inkompatibel.',
|
||||||
@@ -311,7 +313,7 @@ final class DiviAdminExtras
|
|||||||
'wp-lit-mcp'
|
'wp-lit-mcp'
|
||||||
),
|
),
|
||||||
'<code>lit_mcp_divi_auto_release</code>'
|
'<code>lit_mcp_divi_auto_release</code>'
|
||||||
) . '</p>';
|
) . '</p></details>';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function version(): string
|
private function version(): string
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ final class DiviCheckCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft die installierte Divi-Version wie der Reiter „Compatibility“: Manifeste, Fingerabdrücke, Modulkatalog,
|
* Prüft die installierte Divi-Version wie der Reiter „Kompatibilität“: Manifeste, Fingerabdrücke, Modulkatalog,
|
||||||
* Format und Schreibtest auf einer Wegwerfseite. Gibt danach den Zustand je Fähigkeit aus und gibt nichts frei.
|
* Format und Schreibtest auf einer Wegwerfseite. Gibt danach den Zustand je Fähigkeit aus und gibt nichts frei.
|
||||||
* Exit-Code 0: Prüfung grün und alle Fähigkeiten nutzbar; 1: Prüfung rot; 2: Prüfung grün, aber mindestens
|
* Exit-Code 0: Prüfung grün und alle Fähigkeiten nutzbar; 1: Prüfung rot; 2: Prüfung grün, aber mindestens
|
||||||
* eine Fähigkeit nicht nutzbar (z. B. Version noch nicht in DiviReleases::TESTED).
|
* eine Fähigkeit nicht nutzbar (z. B. Version noch nicht in DiviReleases::TESTED).
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ final class ReleaseStatus
|
|||||||
public const SOURCE_AUTO = CompatibilityMatrix::SOURCE_AUTO;
|
public const SOURCE_AUTO = CompatibilityMatrix::SOURCE_AUTO;
|
||||||
|
|
||||||
private const FEATURE_UNTESTED = 'Achtung: %1$s ist auf dieser Website für %2$s noch nicht für LIT MCP '
|
private const FEATURE_UNTESTED = 'Achtung: %1$s ist auf dieser Website für %2$s noch nicht für LIT MCP '
|
||||||
. 'freigegeben. Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Compatibility die '
|
. 'freigegeben. Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Kompatibilität die '
|
||||||
. 'Prüfung ausführt und die Version freigibt. Es wurde nichts geändert.';
|
. 'Prüfung ausführt und die Version freigibt. Es wurde nichts geändert.';
|
||||||
private const FEATURE_UNTESTED_HINT = 'Gesperrt: %s. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre '
|
private const FEATURE_UNTESTED_HINT = 'Gesperrt: %s. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre '
|
||||||
. 'Fähigkeiten freigegeben sind. Den Nutzer bitten, die Divi-Prüfung auszuführen und die Version freizugeben.';
|
. 'Fähigkeiten freigegeben sind. Den Nutzer bitten, die Divi-Prüfung auszuführen und die Version freizugeben.';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# End-to-end checks of the Pods profile of the compatibility layer (plan 2026-11-06-kompatibilitaetsschicht, 1.25):
|
# End-to-end checks of the Pods profile of the compatibility layer (plan 2026-11-06-kompatibilitaetsschicht, 1.25):
|
||||||
# status, check route /pods-check, tab "Compatibility", release (refused for a tested version, simulated for an
|
# status, check route /pods-check, tab "Kompatibilität", release (refused for a tested version, simulated for an
|
||||||
# untested one) and the lock by a red stored report. Runs INSIDE the DDEV web container:
|
# untested one) and the lock by a red stored report. Runs INSIDE the DDEV web container:
|
||||||
# cd /home/janniclas/Projekte/WordPress && timeout 600 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/pods-compat-smoke.sh
|
# cd /home/janniclas/Projekte/WordPress && timeout 600 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/pods-compat-smoke.sh
|
||||||
# Temporarily activates the installed Pods plugin (restored afterwards); removes the lit_mcp_pods_* options, the
|
# Temporarily activates the installed Pods plugin (restored afterwards); removes the lit_mcp_pods_* options, the
|
||||||
|
|||||||
@@ -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
|
private static function html(CompatibilityTab $tab): string
|
||||||
{
|
{
|
||||||
ob_start();
|
ob_start();
|
||||||
@@ -181,7 +189,166 @@ final class CompatibilityTabTest extends TestCase
|
|||||||
|
|
||||||
self::assertInstanceOf(AdminTab::class, $tab);
|
self::assertInstanceOf(AdminTab::class, $tab);
|
||||||
self::assertSame('compatibility', $tab->slug());
|
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
|
public function testAnInactiveProfileIsOneLineAndNeverAsksForTheMatrix(): void
|
||||||
@@ -190,10 +357,13 @@ final class CompatibilityTabTest extends TestCase
|
|||||||
// ReleasePerVersion with a matrix closure that fails the test when called.
|
// ReleasePerVersion with a matrix closure that fails the test when called.
|
||||||
$html = self::html($this->tab(self::profile(Tier::ReleasePerVersion, null, active: false)));
|
$html = self::html($this->tab(self::profile(Tier::ReleasePerVersion, null, active: false)));
|
||||||
|
|
||||||
self::assertStringContainsString('nicht installiert oder nicht aktiv', $html);
|
self::assertStringContainsString('<strong>nicht aktiv</strong>', $html);
|
||||||
self::assertStringContainsString('Fake <Plugin>', $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('<form', $html);
|
||||||
self::assertStringNotContainsString('<table', $html);
|
self::assertStringNotContainsString('href="#lit-mcp-compat-fake"', $html);
|
||||||
|
self::assertSame(1, substr_count($html, '<table'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider states */
|
/** @dataProvider states */
|
||||||
@@ -243,11 +413,8 @@ final class CompatibilityTabTest extends TestCase
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'release' => [Tier::ReleasePerVersion, 'Freigabe pro Version nötig'],
|
'release' => [Tier::ReleasePerVersion, 'Freigabe pro Version nötig'],
|
||||||
'lock' => [
|
'lock' => [Tier::LockOnFailure, 'Sperrt automatisch bei Problemen'],
|
||||||
Tier::LockOnFailure,
|
'report' => [Tier::ReportOnly, 'Nur Anzeige'],
|
||||||
'Freigabe wird dokumentiert, Nutzung automatisch, solange die Prüfung grün ist',
|
|
||||||
],
|
|
||||||
'report' => [Tier::ReportOnly, 'nur Anzeige'],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,8 +456,8 @@ final class CompatibilityTabTest extends TestCase
|
|||||||
{
|
{
|
||||||
$html = self::html($this->tab(self::profile()));
|
$html = self::html($this->tab(self::profile()));
|
||||||
|
|
||||||
self::assertStringContainsString('Getestete Versionen', $html);
|
self::assertStringContainsString('<th scope="row">Im Plugin getestet</th>', $html);
|
||||||
self::assertStringContainsString('Freigegebene Versionen', $html);
|
self::assertStringContainsString('<th scope="row">Von dir freigegeben</th>', $html);
|
||||||
self::assertSame(2, substr_count($html, '<td>–</td>'));
|
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()));
|
$html = self::html($this->tab(self::profile(), self::report()));
|
||||||
|
|
||||||
self::assertLessThan(strpos($html, 'lit-mcp-compat-details'), strpos($html, '[INTRO]'));
|
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
|
public function testAnInactiveProfileFiresNeitherIntroNorExtra(): void
|
||||||
@@ -542,7 +709,7 @@ final class CompatibilityTabTest extends TestCase
|
|||||||
$html = self::html($this->tab(self::profile()));
|
$html = self::html($this->tab(self::profile()));
|
||||||
|
|
||||||
self::assertStringContainsString('<th scope="col">Gut</th>', $html);
|
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);
|
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="Kaputt">–</td>', $html);
|
||||||
self::assertStringContainsString('<td data-label="Heil"><ul><li>ok</li></ul></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('fake capability column "Kaputt" failed', $logged);
|
||||||
self::assertStringContainsString('Zelle <kaputt>', $logged);
|
self::assertStringContainsString('Zelle <kaputt>', $logged);
|
||||||
}
|
}
|
||||||
@@ -591,7 +758,10 @@ final class CompatibilityTabTest extends TestCase
|
|||||||
{
|
{
|
||||||
Filters\expectApplied(CompatibilityTab::COLUMNS_FILTER)->once()->andReturn('kaputt');
|
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
|
public function testEverythingFromThirdPartiesIsEscaped(): void
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ final class SettingsPageTest extends TestCase
|
|||||||
static fn (array $tab): bool => $tab['id'] === 'lit-mcp-overview'
|
static fn (array $tab): bool => $tab['id'] === 'lit-mcp-overview'
|
||||||
&& str_contains($tab['content'], 'Tab „Verbindung“')
|
&& str_contains($tab['content'], 'Tab „Verbindung“')
|
||||||
&& str_contains($tab['content'], 'Tab „Tools“')
|
&& 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);
|
(new SettingsPage([$this->tab('verbindung', 'Verbindung')]))->helpTabFor($screen);
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ final class DiviAdminExtrasTest extends TestCase
|
|||||||
self::assertStringContainsString('Seiten schreiben, Modulkatalog', $html);
|
self::assertStringContainsString('Seiten schreiben, Modulkatalog', $html);
|
||||||
self::assertStringContainsString('alle anderen laufen weiter', $html);
|
self::assertStringContainsString('alle anderen laufen weiter', $html);
|
||||||
self::assertStringContainsString('page=lit-mcp&tab=compatibility', $html);
|
self::assertStringContainsString('page=lit-mcp&tab=compatibility', $html);
|
||||||
self::assertStringContainsString('Reiter „Compatibility“', $html);
|
self::assertStringContainsString('Reiter „Kompatibilität“', $html);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNoNoticeWhileEveryFeatureIsUsable(): void
|
public function testNoNoticeWhileEveryFeatureIsUsable(): void
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ final class ReleaseStatusTest extends TestCase
|
|||||||
|
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
'Achtung: Divi 5.3.0 ist auf dieser Website für „Seiten schreiben“ noch nicht für LIT MCP freigegeben. '
|
'Achtung: Divi 5.3.0 ist auf dieser Website für „Seiten schreiben“ noch nicht für LIT MCP freigegeben. '
|
||||||
. 'Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Compatibility die Prüfung ausführt '
|
. 'Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Kompatibilität die Prüfung ausführt '
|
||||||
. 'und die Version freigibt. Es wurde nichts geändert.',
|
. 'und die Version freigibt. Es wurde nichts geändert.',
|
||||||
$payload['message']
|
$payload['message']
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -172,8 +172,10 @@ final class PluginTest extends TestCase
|
|||||||
|
|
||||||
self::assertInstanceOf(CompatibilityTab::class, $tab);
|
self::assertInstanceOf(CompatibilityTab::class, $tab);
|
||||||
self::assertSame('compatibility', $tab->slug());
|
self::assertSame('compatibility', $tab->slug());
|
||||||
self::assertStringContainsString('id="lit-mcp-compat-divi"', $html);
|
// Both plugins are inactive in this registry: one overview row each, no section.
|
||||||
self::assertStringContainsString('id="lit-mcp-compat-pods"', $html);
|
self::assertStringContainsString('data-label="Plugin">Divi</th>', $html);
|
||||||
|
self::assertStringContainsString('data-label="Plugin">Pods</th>', $html);
|
||||||
|
self::assertStringNotContainsString('<section', $html);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function readOnly(object $source): bool
|
private static function readOnly(object $source): bool
|
||||||
|
|||||||
Reference in New Issue
Block a user