feat: tools and limits settings tabs
Adds Einstellungen → LIT MCP → Tools (direct/indirect/disabled per ability, only ExposurePolicy-allowed modes offered) and → Limits (upload size, log retention, snapshot count), wired into Plugin.php. Ruling Q11b (carried from task 6): overview.md notes that direct visibility is configurable; hints in src/ that name a tool which is direct by default but can be switched to indirect (content-get, content-query, content-types, post-publish, media-search) now use the neutral "lit/<name> (direkt: lit-<name>)" form so they stay correct after an admin override. lit/guide (always direct) and the media-preview/-download DIRECT_ONLY tools are unaffected. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -21,6 +21,9 @@ Direkt sichtbar: `lit-guide`, `lit-site-status`, `lit-content-types`, `lit-conte
|
||||
`lit-content-get`, `lit-content-create`, `lit-content-update`, `lit-post-publish`, `lit-media-search`,
|
||||
`lit-media-preview`, `lit-media-download`, `lit-media-upload-from-url`.
|
||||
|
||||
Welche Tools direkt sichtbar sind, lässt sich unter Einstellungen → LIT MCP → Tools ändern (außer bei
|
||||
`lit-guide`, das immer direkt bleibt).
|
||||
|
||||
Über `mcp-adapter-discover-abilities` / `mcp-adapter-execute-ability` (Ability-Namen `lit/…`; in Guides und
|
||||
Hinweisen immer so geschrieben):
|
||||
Status (`lit/post-unpublish`, `lit/post-schedule`, `lit/post-trash`), Kopien (`lit/post-duplicate`,
|
||||
|
||||
@@ -50,7 +50,8 @@ final class Errors
|
||||
|
||||
public static function notFound(
|
||||
string $message,
|
||||
string $hint = 'ID prüfen, z. B. über lit-content-query oder lit-media-search suchen.'
|
||||
string $hint = 'ID prüfen, z. B. über lit/content-query (direkt: lit-content-query) oder lit/media-search '
|
||||
. '(direkt: lit-media-search) suchen.'
|
||||
): WP_Error {
|
||||
return self::make('not_found', $message, $hint, 404);
|
||||
}
|
||||
@@ -60,7 +61,8 @@ final class Errors
|
||||
return self::make(
|
||||
'rev_mismatch',
|
||||
'Der Inhalt wurde seit dem letzten Lesen geändert; nichts wurde geschrieben.',
|
||||
'Neu lesen (z. B. lit-content-get) und die Änderung auf dem aktuellen Stand wiederholen.',
|
||||
'Neu lesen (z. B. lit/content-get, direkt: lit-content-get) und die Änderung auf dem aktuellen Stand '
|
||||
. 'wiederholen.',
|
||||
409
|
||||
);
|
||||
}
|
||||
@@ -144,10 +146,10 @@ final class Errors
|
||||
'partial_write',
|
||||
trim('Speichern fehlgeschlagen: ' . $reason) . ' Die Änderung konnte nicht vollständig zurückgenommen '
|
||||
. 'werden. ' . $state,
|
||||
$hint ?? 'Den Aufruf nicht einfach wiederholen. Zuerst den aktuellen Stand prüfen (lit-content-get, bei '
|
||||
. 'einem neuen Entwurf lit-content-query) und ggf. mit lit/revisions-list und lit/revision-restore '
|
||||
. '(über mcp-adapter-execute-ability) den vorherigen Stand wiederherstellen; dem Nutzer Bescheid '
|
||||
. 'geben.',
|
||||
$hint ?? 'Den Aufruf nicht einfach wiederholen. Zuerst den aktuellen Stand prüfen (lit/content-get, '
|
||||
. 'direkt: lit-content-get; bei einem neuen Entwurf lit/content-query, direkt: lit-content-query) '
|
||||
. 'und ggf. mit lit/revisions-list und lit/revision-restore (über mcp-adapter-execute-ability) den '
|
||||
. 'vorherigen Stand wiederherstellen; dem Nutzer Bescheid geben.',
|
||||
500,
|
||||
$details
|
||||
);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Admin;
|
||||
|
||||
use LIT\MCP\Limits;
|
||||
|
||||
/**
|
||||
* Tab "Limits": upload/download size, log retention, snapshot count (spec §8.4).
|
||||
* Changes apply immediately – Limits::all() always reads the current option.
|
||||
*/
|
||||
final class LimitsTab implements AdminTab
|
||||
{
|
||||
public const GROUP = 'lit_mcp_limits';
|
||||
|
||||
public function slug(): string
|
||||
{
|
||||
return 'limits';
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return __('Limits', 'wp-lit-mcp');
|
||||
}
|
||||
|
||||
public function registerSettings(): void
|
||||
{
|
||||
register_setting(self::GROUP, Limits::OPTION, [
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => [Limits::class, 'sanitize'],
|
||||
'default' => Limits::DEFAULTS,
|
||||
]);
|
||||
}
|
||||
|
||||
public function registerActions(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function render(): void
|
||||
{
|
||||
$values = (new Limits())->all();
|
||||
$fields = [
|
||||
'upload_mb' => [__('Maximale Upload-/Download-Größe (MB)', 'wp-lit-mcp'), 1, 100],
|
||||
'log_retention_days' => [__('Protokoll aufbewahren (Tage)', 'wp-lit-mcp'), 1, 3650],
|
||||
'snapshot_count' => [__('Anzahl Designsystem-Snapshots (ab Etappe 4)', 'wp-lit-mcp'), 1, 100],
|
||||
];
|
||||
|
||||
echo '<form method="post" action="options.php">';
|
||||
settings_fields(self::GROUP);
|
||||
echo '<table class="form-table"><tbody>';
|
||||
foreach ($fields as $key => [$label, $min, $max]) {
|
||||
printf(
|
||||
'<tr><th scope="row"><label for="lit-mcp-%1$s">%2$s</label></th><td>'
|
||||
. '<input id="lit-mcp-%1$s" type="number" min="%3$d" max="%4$d" name="%5$s[%1$s]" value="%6$d">'
|
||||
. '</td></tr>',
|
||||
esc_attr($key),
|
||||
esc_html($label),
|
||||
esc_attr((string) $min),
|
||||
esc_attr((string) $max),
|
||||
esc_attr(Limits::OPTION),
|
||||
esc_attr((string) $values[$key])
|
||||
);
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
submit_button();
|
||||
echo '</form>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Admin;
|
||||
|
||||
use LIT\MCP\Abilities\ExposurePolicy;
|
||||
use LIT\MCP\Integrations\IntegrationRegistry;
|
||||
|
||||
/**
|
||||
* Tab "Tools": direct/indirect/disabled exposure per ability (spec §8.4). Only the modes
|
||||
* ExposurePolicy::isAllowed() allows for a given ability are offered; "Standard" saves nothing,
|
||||
* so a later version's changed default takes effect automatically.
|
||||
*/
|
||||
final class ToolsTab implements AdminTab
|
||||
{
|
||||
public const GROUP = 'lit_mcp_tools';
|
||||
|
||||
public function __construct(private IntegrationRegistry $registry, private ExposurePolicy $policy)
|
||||
{
|
||||
}
|
||||
|
||||
public function slug(): string
|
||||
{
|
||||
return 'tools';
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return __('Tools', 'wp-lit-mcp');
|
||||
}
|
||||
|
||||
public function registerSettings(): void
|
||||
{
|
||||
register_setting(self::GROUP, ExposurePolicy::OPTION, [
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => [ExposurePolicy::class, 'sanitize'],
|
||||
'default' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function registerActions(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function render(): void
|
||||
{
|
||||
echo '<p>'
|
||||
. esc_html__('Direkt: sofort als eigenes Tool sichtbar.', 'wp-lit-mcp') . ' '
|
||||
. esc_html__('Indirekt: nur über die Meta-Tools (discover/execute) erreichbar.', 'wp-lit-mcp') . ' '
|
||||
. esc_html__('Deaktiviert: gar nicht verfügbar.', 'wp-lit-mcp')
|
||||
. '</p>';
|
||||
echo '<p>'
|
||||
. esc_html__(
|
||||
'Die Server-Anweisungen beim Verbinden nennen immer die aktuell direkt sichtbaren Tools.',
|
||||
'wp-lit-mcp'
|
||||
) . ' '
|
||||
. esc_html__(
|
||||
'Änderungen gelten also ab der nächsten Verbindung; in claude.ai ggf. den Connector neu verbinden.',
|
||||
'wp-lit-mcp'
|
||||
)
|
||||
. '</p>';
|
||||
echo '<p>' . esc_html__(
|
||||
'Das Divi-Code-Modul ist immer erlaubt (nur nachrangig) und hat keine eigene Einstellung.',
|
||||
'wp-lit-mcp'
|
||||
) . '</p>';
|
||||
echo '<form method="post" action="options.php">';
|
||||
settings_fields(self::GROUP);
|
||||
echo '<table class="widefat striped"><thead><tr><th>' . esc_html__('Tool', 'wp-lit-mcp') . '</th><th>'
|
||||
. esc_html__('Einstellung', 'wp-lit-mcp') . '</th></tr></thead><tbody>';
|
||||
|
||||
foreach ($this->registry->abilities() as $ability) {
|
||||
$name = $ability->name();
|
||||
$current = $this->policy->mode($ability);
|
||||
$isDefault = $current === $this->policy->defaultMode($ability);
|
||||
printf(
|
||||
'<tr><td><strong>%s</strong><br><code>%s</code></td><td><select name="%s[%s]">',
|
||||
esc_html($ability->label()),
|
||||
esc_html($name),
|
||||
esc_attr(ExposurePolicy::OPTION),
|
||||
esc_attr($name)
|
||||
);
|
||||
printf(
|
||||
'<option value=""%s>%s</option>',
|
||||
selected($isDefault, true, false),
|
||||
esc_html(sprintf(
|
||||
/* translators: %s: default mode */
|
||||
__('Standard (%s)', 'wp-lit-mcp'),
|
||||
$this->modeLabel($this->policy->defaultMode($ability))
|
||||
))
|
||||
);
|
||||
foreach (ExposurePolicy::MODES as $mode) {
|
||||
if (! ExposurePolicy::isAllowed($name, $mode)) {
|
||||
continue;
|
||||
}
|
||||
printf(
|
||||
'<option value="%s"%s>%s</option>',
|
||||
esc_attr($mode),
|
||||
selected(! $isDefault && $current === $mode, true, false),
|
||||
esc_html($this->modeLabel($mode))
|
||||
);
|
||||
}
|
||||
echo '</select></td></tr>';
|
||||
}
|
||||
|
||||
echo '</tbody></table>';
|
||||
submit_button();
|
||||
echo '</form>';
|
||||
}
|
||||
|
||||
private function modeLabel(string $mode): string
|
||||
{
|
||||
return match ($mode) {
|
||||
ExposurePolicy::DIRECT => __('direkt', 'wp-lit-mcp'),
|
||||
ExposurePolicy::INDIRECT => __('indirekt', 'wp-lit-mcp'),
|
||||
default => __('deaktiviert', 'wp-lit-mcp'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ final class InstructionsBuilder
|
||||
|
||||
if ($this->postTypeNames !== null) {
|
||||
$lines[] = 'Inhaltstypen: ' . implode(', ', ($this->postTypeNames)())
|
||||
. ' (Felder und Taxonomien: lit-content-types).';
|
||||
. ' (Felder und Taxonomien: lit/content-types, direkt: lit-content-types).';
|
||||
}
|
||||
|
||||
return implode("\n", array_merge($lines, [
|
||||
|
||||
@@ -35,7 +35,7 @@ final class MediaGuard
|
||||
if (! $attachment instanceof WP_Post || $attachment->post_type !== 'attachment') {
|
||||
return Errors::notFound(
|
||||
sprintf('Medium %d nicht gefunden.', $attachmentId),
|
||||
'ID prüfen; Medien über lit-media-search suchen.'
|
||||
'ID prüfen; Medien über lit/media-search (direkt: lit-media-search) suchen.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ final class MediaUpdateAbility implements Ability
|
||||
|
||||
private const REASON = 'WordPress hat die Metadaten eines Mediums nicht gespeichert.';
|
||||
|
||||
private const REV_HINT = 'Die genannten Medien neu lesen (lit/media-get oder lit-media-search) und mit dem '
|
||||
. 'aktuellen modified erneut senden.';
|
||||
private const REV_HINT = 'Die genannten Medien neu lesen (lit/media-get oder lit/media-search, direkt: '
|
||||
. 'lit-media-search) und mit dem aktuellen modified erneut senden.';
|
||||
|
||||
public function __construct(private ChangeLog $log, private MediaMetaWriter $writer = new MediaMetaWriter())
|
||||
{
|
||||
@@ -284,8 +284,9 @@ final class MediaUpdateAbility implements Ability
|
||||
$cause,
|
||||
$detail,
|
||||
rolledBackHint: 'Das Zurücksetzen hat modified der Medien geändert: vor einem neuen Versuch die Medien '
|
||||
. 'neu lesen (lit/media-get oder lit-media-search) und den Stapel mit dem aktuellen modified '
|
||||
. 'senden; tritt der Fehler wieder auf, den Administrator informieren (Details im PHP-Error-Log).'
|
||||
. 'neu lesen (lit/media-get oder lit/media-search, direkt: lit-media-search) und den Stapel mit '
|
||||
. 'dem aktuellen modified senden; tritt der Fehler wieder auf, den Administrator informieren '
|
||||
. '(Details im PHP-Error-Log).'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -399,8 +400,8 @@ final class MediaUpdateAbility implements Ability
|
||||
return match (count($codes) === 1 ? $codes[0] : 'mixed') {
|
||||
'forbidden' => Errors::make('forbidden', $message, 'Nur Medien ändern, die das verbundene Konto '
|
||||
. 'bearbeiten darf; die übrigen Einträge erneut senden.', 403, ['failures' => $failures]),
|
||||
'not_found' => Errors::make('not_found', $message, 'IDs prüfen (lit-media-search) und die Einträge '
|
||||
. 'korrigiert erneut senden.', 404, ['failures' => $failures]),
|
||||
'not_found' => Errors::make('not_found', $message, 'IDs prüfen (lit/media-search, direkt: '
|
||||
. 'lit-media-search) und die Einträge korrigiert erneut senden.', 404, ['failures' => $failures]),
|
||||
'rev_mismatch' => Errors::make('rev_mismatch', $message, self::REV_HINT, 409, ['failures' => $failures]),
|
||||
default => Errors::make('validation_failed', $message, 'Die unter failures genannten Einträge '
|
||||
. 'korrigieren und den ganzen Stapel erneut senden.', 400, ['failures' => $failures]),
|
||||
|
||||
@@ -88,8 +88,8 @@ final class PostGuard
|
||||
(int) $post->ID
|
||||
),
|
||||
[$field => 'geplanter Zeitpunkt verstrichen'],
|
||||
'Beim Nutzer nachfragen: jetzt veröffentlichen (lit-post-publish) oder neu planen (lit/post-schedule '
|
||||
. 'über mcp-adapter-execute-ability); danach die Änderung wiederholen.'
|
||||
'Beim Nutzer nachfragen: jetzt veröffentlichen (lit/post-publish, direkt: lit-post-publish) oder neu '
|
||||
. 'planen (lit/post-schedule über mcp-adapter-execute-ability); danach die Änderung wiederholen.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ final class PostGuard
|
||||
if (! is_object($object)) {
|
||||
return Errors::notFound(
|
||||
sprintf('Inhaltstyp "%s" ist nicht verfügbar.', $name),
|
||||
'Verfügbare Inhaltstypen über lit-content-types abrufen.'
|
||||
'Verfügbare Inhaltstypen über lit/content-types (direkt: lit-content-types) abrufen.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -289,8 +289,8 @@ final class PostReplaceWithCopyAbility implements Ability
|
||||
$revisionId === null ? '–' : (string) $revisionId,
|
||||
$backupId
|
||||
),
|
||||
'Den Aufruf nicht einfach wiederholen. Zuerst den Stand mit lit-content-get prüfen; Titel, Inhalt '
|
||||
. 'und Auszug lassen sich mit lit/revision-restore (revision_id, über '
|
||||
'Den Aufruf nicht einfach wiederholen. Zuerst den Stand mit lit/content-get (direkt: lit-content-get) '
|
||||
. 'prüfen; Titel, Inhalt und Auszug lassen sich mit lit/revision-restore (revision_id, über '
|
||||
. 'mcp-adapter-execute-ability) zurückholen, Felder/Meta und Begriffe stehen in der Sicherung '
|
||||
. 'backup_id (in WordPress aus dem Papierkorb holen). Dem Nutzer Bescheid geben.',
|
||||
$details
|
||||
|
||||
@@ -103,7 +103,7 @@ final class PostScheduleAbility implements Ability
|
||||
return Errors::invalidInput(
|
||||
'Der Zeitpunkt muss in der Zukunft liegen.',
|
||||
['date' => 'liegt nicht in der Zukunft'],
|
||||
'Zum sofortigen Veröffentlichen lit-post-publish verwenden.'
|
||||
'Zum sofortigen Veröffentlichen lit/post-publish (direkt: lit-post-publish) verwenden.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ final class StatusWrite
|
||||
$e,
|
||||
'',
|
||||
sprintf('Status von Eintrag %d ist möglicherweise schon geändert worden.', $postId),
|
||||
'Den Aufruf nicht einfach wiederholen. Zuerst mit lit-content-get Status und Inhalt prüfen und dem '
|
||||
. 'Nutzer Bescheid geben.'
|
||||
'Den Aufruf nicht einfach wiederholen. Zuerst mit lit/content-get (direkt: lit-content-get) Status '
|
||||
. 'und Inhalt prüfen und dem Nutzer Bescheid geben.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ use LIT\MCP\Admin\AdminNotices;
|
||||
use LIT\MCP\Admin\ConnectionRepository;
|
||||
use LIT\MCP\Admin\ConnectionStatus;
|
||||
use LIT\MCP\Admin\ConnectionTab;
|
||||
use LIT\MCP\Admin\LimitsTab;
|
||||
use LIT\MCP\Admin\SettingsPage;
|
||||
use LIT\MCP\Admin\SiteHealth;
|
||||
use LIT\MCP\Admin\ToolsTab;
|
||||
use LIT\MCP\ChangeLog\ChangeLog;
|
||||
use LIT\MCP\ChangeLog\ChangeLogCleanup;
|
||||
use LIT\MCP\ChangeLog\ChangeLogInstaller;
|
||||
@@ -82,6 +84,8 @@ final class Plugin
|
||||
self::$settingsPage = new SettingsPage([
|
||||
new ConnectionTab($status),
|
||||
new AccessTab($access, new ConnectionRepository()),
|
||||
new ToolsTab($registry, $policy),
|
||||
new LimitsTab(),
|
||||
]);
|
||||
if (is_admin()) {
|
||||
self::$settingsPage->register();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Admin;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Admin\LimitsTab;
|
||||
use LIT\MCP\Limits;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
|
||||
final class LimitsTabTest extends TestCase
|
||||
{
|
||||
public function testRegistersTheLimitsOption(): void
|
||||
{
|
||||
Functions\expect('register_setting')->once()->with(
|
||||
'lit_mcp_limits',
|
||||
'lit_mcp_limits',
|
||||
Mockery::on(static fn (array $args): bool => $args['sanitize_callback'] === [Limits::class, 'sanitize']
|
||||
&& $args['default'] === Limits::DEFAULTS)
|
||||
);
|
||||
|
||||
(new LimitsTab())->registerSettings();
|
||||
}
|
||||
|
||||
public function testRendersThreeNumberFieldsWithCurrentValues(): void
|
||||
{
|
||||
Functions\stubEscapeFunctions();
|
||||
Functions\stubTranslationFunctions();
|
||||
Functions\when('settings_fields')->justReturn(null);
|
||||
Functions\when('submit_button')->justReturn(null);
|
||||
Functions\when('get_option')->justReturn(['upload_mb' => 8]);
|
||||
|
||||
ob_start();
|
||||
(new LimitsTab())->render();
|
||||
$html = (string) ob_get_clean();
|
||||
|
||||
self::assertSame(3, substr_count($html, 'type="number"'));
|
||||
self::assertStringContainsString('name="lit_mcp_limits[upload_mb]" value="8"', $html);
|
||||
self::assertStringContainsString('name="lit_mcp_limits[log_retention_days]" value="90"', $html);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Admin;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Abilities\Ability;
|
||||
use LIT\MCP\Abilities\ExposurePolicy;
|
||||
use LIT\MCP\Admin\ToolsTab;
|
||||
use LIT\MCP\Integrations\Integration;
|
||||
use LIT\MCP\Integrations\IntegrationRegistry;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
|
||||
final class ToolsTabTest extends TestCase
|
||||
{
|
||||
private function registry(): IntegrationRegistry
|
||||
{
|
||||
$abilities = [];
|
||||
foreach ([['lit/guide', true], ['lit/media-preview', true], ['lit/menu-list', false]] as [$name, $direct]) {
|
||||
$ability = Mockery::mock(Ability::class);
|
||||
$ability->allows('name')->andReturn($name);
|
||||
$ability->allows('label')->andReturn('Label ' . $name);
|
||||
$ability->allows('isDirect')->andReturn($direct);
|
||||
$abilities[] = $ability;
|
||||
}
|
||||
$integration = Mockery::mock(Integration::class);
|
||||
$integration->allows('isAvailable')->andReturn(true);
|
||||
$integration->allows('abilities')->andReturn($abilities);
|
||||
|
||||
return new IntegrationRegistry($integration);
|
||||
}
|
||||
|
||||
public function testRegistersTheExposureOptionWithThePolicySanitizer(): void
|
||||
{
|
||||
Functions\expect('register_setting')->once()->with(
|
||||
'lit_mcp_tools',
|
||||
'lit_mcp_exposure',
|
||||
Mockery::on(static fn (array $args): bool => $args['sanitize_callback'] === [ExposurePolicy::class, 'sanitize'])
|
||||
);
|
||||
|
||||
(new ToolsTab($this->registry(), new ExposurePolicy()))->registerSettings();
|
||||
}
|
||||
|
||||
public function testRendersOneSelectPerAbilityWithOnlyAllowedModes(): void
|
||||
{
|
||||
Functions\stubEscapeFunctions();
|
||||
Functions\stubTranslationFunctions();
|
||||
Functions\when('settings_fields')->justReturn(null);
|
||||
Functions\when('submit_button')->justReturn(null);
|
||||
Functions\when('selected')->alias(static fn ($a, $b = true, $echo = true): string => (string) $a === (string) $b ? ' selected="selected"' : '');
|
||||
|
||||
ob_start();
|
||||
(new ToolsTab($this->registry(), new ExposurePolicy(['lit/menu-list' => 'disabled'])))->render();
|
||||
$html = (string) ob_get_clean();
|
||||
|
||||
$selects = array_slice(explode('<select', $html), 1);
|
||||
self::assertCount(3, $selects);
|
||||
[$guide, $preview, $menu] = $selects;
|
||||
self::assertStringContainsString('lit_mcp_exposure[lit/guide]', $guide);
|
||||
self::assertStringNotContainsString('value="disabled"', $guide);
|
||||
self::assertStringNotContainsString('value="indirect"', $preview);
|
||||
self::assertStringContainsString('value="disabled" selected="selected"', $menu);
|
||||
self::assertStringContainsString('value="" selected="selected"', $guide);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user