refactor: Kern ohne Divi – Ports BuilderDetector, MediaUsageSource, EnvironmentComponent und ausgeschlossene Post-Typen im ContentSchema; Divi-Implementierungen und Verdrahtung über DiviIntegration/Plugin; CoreIndependenceTest

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 21:49:28 +02:00
co-authored by Claude Sonnet 5.5
parent abeb4b0eed
commit 06148fb8c1
33 changed files with 952 additions and 154 deletions
@@ -13,6 +13,7 @@ use LIT\MCP\Integrations\WordPress\Content\ContentUpdateAbility;
use LIT\MCP\Integrations\WordPress\Content\PostPresenter;
use LIT\MCP\Integrations\WordPress\Content\PostWriter;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Tests\Support\FakeBuilderDetector;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\SchemaFixture;
use LIT\MCP\Tests\Unit\TestCase;
@@ -46,9 +47,12 @@ final class ContentUpdateAbilityTest extends TestCase
return $schema;
}
private function ability(PostWriter $writer, ?ChangeLog $log = null): ContentUpdateAbility
{
return new ContentUpdateAbility($this->schema(), $writer, $log ?? FakeChangeLog::forbidden());
private function ability(
PostWriter $writer,
?ChangeLog $log = null,
?PostPresenter $presenter = null
): ContentUpdateAbility {
return new ContentUpdateAbility($this->schema(), $writer, $log ?? FakeChangeLog::forbidden(), $presenter);
}
public function testRevIsRequired(): void
@@ -110,11 +114,11 @@ final class ContentUpdateAbilityTest extends TestCase
public function testRawContentOnDiviPagesIsUnsupported(): void
{
Functions\when('get_post_meta')->justReturn('on');
$writer = Mockery::mock(PostWriter::class);
$writer->expects('prepare')->never();
$presenter = new PostPresenter($this->schema(), new FakeBuilderDetector(false, [5]));
$result = $this->ability($writer)
$result = $this->ability($writer, null, $presenter)
->execute(['post_id' => 5, 'rev' => Rev::ofPost($this->post), 'content' => '<p>Neu</p>']);
self::assertToolError($result, 'unsupported');
@@ -10,6 +10,7 @@ use LIT\MCP\Integrations\WordPress\Content\PostPresenter;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Integrations\WordPress\Schema\FieldDefinition;
use LIT\MCP\Integrations\WordPress\Schema\FieldSource;
use LIT\MCP\Tests\Support\FakeBuilderDetector;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use stdClass;
@@ -75,19 +76,21 @@ final class PostPresenterTest extends TestCase
public function testDiviContentIsWithheldAndPointsToTheDiviOutline(): void
{
$this->stubLinks();
Functions\when('get_post_meta')->alias(static fn (int $id, string $key) => $key === '_et_pb_use_builder' ? 'on' : '');
Functions\when('get_post_meta')->justReturn('');
Functions\when('get_post_thumbnail_id')->justReturn(0);
$schema = Mockery::mock(ContentSchema::class);
$schema->allows('taxonomies')->andReturn([]);
$schema->allows('fields')->andReturn([]);
$post = new WP_Post(['ID' => 5, 'post_type' => 'page', 'post_content' => '<!-- wp:divi/section /-->']);
$full = (new PostPresenter($schema))->full($post);
$presenter = new PostPresenter($schema, new FakeBuilderDetector(false, [5]));
$full = $presenter->full($post);
self::assertTrue($full['divi']);
self::assertNull($full['content']);
self::assertNull($full['featured_image']);
self::assertSame([PostPresenter::diviHint(false)], $full['hints']);
self::assertSame([$presenter->diviHint(false)], $full['hints']);
self::assertInstanceOf(stdClass::class, $full['terms']);
self::assertInstanceOf(stdClass::class, $full['fields']);
self::assertSame(Rev::ofPost($post), $full['rev']);
@@ -95,14 +98,35 @@ final class PostPresenterTest extends TestCase
public function testDiviHintNamesTheDiviToolsOnlyWhenDiviIsActive(): void
{
self::assertStringContainsString('lit-divi-outline', PostPresenter::diviHint(true));
self::assertStringContainsString('lit-content-update', PostPresenter::diviHint(true));
$presenter = new PostPresenter(Mockery::mock(ContentSchema::class));
self::assertStringContainsString('lit-divi-outline', $presenter->diviHint(true));
self::assertStringContainsString('lit-content-update', $presenter->diviHint(true));
foreach (['lit-divi-insert', 'lit-divi-update', 'lit-divi-move', 'lit-divi-delete'] as $tool) {
self::assertStringContainsString($tool, PostPresenter::diviHint(true));
self::assertStringContainsString($tool, $presenter->diviHint(true));
}
self::assertStringNotContainsString('vorerst im Divi-Builder', PostPresenter::diviHint(true));
self::assertStringNotContainsString('lit-divi', PostPresenter::diviHint(false));
self::assertStringContainsString(PostPresenter::DIVI_INACTIVE_NOTE, PostPresenter::diviHint());
self::assertStringNotContainsString('vorerst im Divi-Builder', $presenter->diviHint(true));
self::assertStringNotContainsString('lit-divi', $presenter->diviHint(false));
self::assertStringContainsString(PostPresenter::DIVI_INACTIVE_NOTE, $presenter->diviHint());
}
public function testTheBuilderDetectorDecidesWhichNoteApplies(): void
{
$schema = Mockery::mock(ContentSchema::class);
self::assertSame(
PostPresenter::DIVI_NOTE,
(new PostPresenter($schema, new FakeBuilderDetector(true)))->diviNote()
);
self::assertSame(
PostPresenter::DIVI_INACTIVE_NOTE,
(new PostPresenter($schema, new FakeBuilderDetector(false)))->diviNote()
);
self::assertSame(
PostPresenter::DIVI_INACTIVE_NOTE,
(new PostPresenter($schema))->diviNote(),
'without a detector no builder is assumed'
);
}
public function testPasswordAndProtectedReadonlyMetaFieldsAreOmitted(): void
@@ -131,9 +155,21 @@ final class PostPresenterTest extends TestCase
public function testDiviIsAlsoDetectedFromBlockMarkup(): void
{
Functions\when('get_post_meta')->justReturn('');
$presenter = new PostPresenter(Mockery::mock(ContentSchema::class), new FakeBuilderDetector(true, []));
self::assertTrue(PostPresenter::usesDivi(new WP_Post(['post_content' => 'x <!-- wp:divi/text {} /--> y'])));
self::assertFalse(PostPresenter::usesDivi(new WP_Post(['post_content' => '<!-- wp:paragraph -->'])));
self::assertTrue($presenter->usesDivi(new WP_Post(['post_content' => 'x <!-- wp:divi/text {} /--> y'])));
self::assertFalse($presenter->usesDivi(new WP_Post(['post_content' => '<!-- wp:paragraph -->'])));
}
public function testDiviIsDetectedFromTheBuilderFlagOfTheDetector(): void
{
$presenter = new PostPresenter(Mockery::mock(ContentSchema::class), new FakeBuilderDetector(true, [7]));
self::assertTrue($presenter->usesDivi(new WP_Post(['ID' => 7, 'post_content' => '[et_pb_section]'])));
self::assertFalse($presenter->usesDivi(new WP_Post(['ID' => 8, 'post_content' => '[et_pb_section]'])));
self::assertFalse(
(new PostPresenter(Mockery::mock(ContentSchema::class)))->usesDivi(new WP_Post(['ID' => 7])),
'without a detector only block markup counts'
);
}
}
@@ -8,6 +8,7 @@ use Brain\Monkey\Functions;
use LIT\MCP\Abilities\Cursor;
use LIT\MCP\Integrations\WordPress\Content\PostPresenter;
use LIT\MCP\Integrations\WordPress\Revisions\RevisionsListAbility;
use LIT\MCP\Tests\Support\FakeBuilderDetector;
use LIT\MCP\Tests\Support\SchemaFixture;
use LIT\MCP\Tests\Unit\TestCase;
use WP_Post;
@@ -24,11 +25,11 @@ final class RevisionsListAbilityTest extends TestCase
Functions\when('get_post_meta')->justReturn('');
}
private function ability(): RevisionsListAbility
private function ability(?PostPresenter $presenter = null): RevisionsListAbility
{
$schema = SchemaFixture::managed();
return new RevisionsListAbility($schema);
return new RevisionsListAbility($schema, $presenter);
}
public function testListsRevisionsNewestFirstWithCursor(): void
@@ -60,11 +61,14 @@ final class RevisionsListAbilityTest extends TestCase
811 => new WP_Post(['ID' => 811, 'post_content' => '<p>Normal</p>']),
]);
$result = $this->ability()->execute(['post_id' => 5]);
$presenter = new PostPresenter(SchemaFixture::managed(), new FakeBuilderDetector(true));
$result = $this->ability($presenter)->execute(['post_id' => 5]);
self::assertTrue($result['items'][0]['divi']);
self::assertFalse($result['items'][1]['divi']);
self::assertSame([PostPresenter::diviHint()], $result['hints']);
self::assertSame([$presenter->diviHint()], $result['hints']);
self::assertStringContainsString(PostPresenter::DIVI_NOTE, $result['hints'][0]);
}
public function testRevisionsOfATrashedPostCanStillBeListed(): void
@@ -37,7 +37,7 @@ final class ContentSchemaTest extends TestCase
{
$this->stubPostTypes();
$schema = new ContentSchema([]);
$schema = new ContentSchema([], null, ['et_pb_layout']);
self::assertSame(['lit_person', 'page', 'post'], $schema->postTypeNames());
self::assertTrue($schema->isManaged('page'));
@@ -80,7 +80,8 @@ final class ContentSchemaTest extends TestCase
new FieldDefinition('footnotes', 'footnotes', 'string', 'meta', 'writable'),
new FieldDefinition('zz_hidden', 'Verborgen', 'string', 'pods', FieldDefinition::HIDDEN),
])],
static fn (string $type): bool => $type === 'page'
static fn (string $type): bool => $type === 'page',
['et_pb_layout']
);
$withoutFields = $schema->describe('page', false);
@@ -118,17 +119,34 @@ final class ContentSchemaTest extends TestCase
self::assertSame(['logo', 'photo'], (new ContentSchema([$source]))->mediaFieldKeys());
}
/** Spec §3: Divi's post types come from the Katalog adapter – the list itself stays the same. */
public function testExcludedPostTypesStayTheSame(): void
public function testWordPressExcludesItsOwnPostTypesAndIntegrationsAddTheirs(): void
{
$this->stubPostTypes();
self::assertSame(
['et_pb_layout', 'lit_person', 'page', 'post'],
(new ContentSchema([]))->postTypeNames(),
'without an integration the Divi layouts are an ordinary post type'
);
self::assertSame(
['page', 'post'],
(new ContentSchema([], null, ['et_pb_layout', 'lit_person']))->postTypeNames()
);
self::assertSame(
[
'attachment', 'nav_menu_item', 'revision', 'wp_block', 'wp_template', 'wp_template_part',
'wp_navigation', 'wp_global_styles', 'wp_font_family', 'wp_font_face', 'et_pb_layout',
'et_theme_builder', 'et_template', 'et_header_layout', 'et_body_layout', 'et_footer_layout',
'et_pb_canvas', '_et_pb_speculation',
'wp_navigation', 'wp_global_styles', 'wp_font_family', 'wp_font_face',
],
ContentSchema::EXCLUDED
);
}
public function testTheBuilderIsNeverOnWithoutACheck(): void
{
self::assertFalse((new ContentSchema([]))->diviEnabled('page'));
$schema = new ContentSchema([], static fn (string $type): bool => $type === 'page');
self::assertTrue($schema->diviEnabled('page'));
self::assertFalse($schema->diviEnabled('post'));
}
}