From 06148fb8c182ad0c4798987feef7c7509186e5f7 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sun, 18 Oct 2026 21:49:28 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20Kern=20ohne=20Divi=20=E2=80=93=20Po?= =?UTF-8?q?rts=20BuilderDetector,=20MediaUsageSource,=20EnvironmentCompone?= =?UTF-8?q?nt=20und=20ausgeschlossene=20Post-Typen=20im=20ContentSchema;?= =?UTF-8?q?=20Divi-Implementierungen=20und=20Verdrahtung=20=C3=BCber=20Div?= =?UTF-8?q?iIntegration/Plugin;=20CoreIndependenceTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- src/Abilities/DiviMarkup.php | 3 + src/Integrations/Core/CoreIntegration.php | 5 +- .../Core/EnvironmentComponent.php | 21 +++ src/Integrations/Core/EnvironmentProbe.php | 26 ++-- src/Integrations/Divi/DiviBuilderDetector.php | 25 +++ .../Divi/DiviEnvironmentComponent.php | 36 +++++ src/Integrations/Divi/DiviIntegration.php | 42 +++++ src/Integrations/Divi/DiviLogoUsage.php | 35 +++++ src/Integrations/Media/AttachmentFile.php | 23 +++ src/Integrations/Media/MediaIntegration.php | 15 +- src/Integrations/Media/MediaUsageFinder.php | 44 +++--- src/Integrations/Media/MediaUsageSource.php | 23 +++ .../WordPress/BuilderDetector.php | 20 +++ .../Content/ContentUpdateAbility.php | 11 +- .../WordPress/Content/PostPresenter.php | 44 +++--- .../WordPress/NoBuilderDetector.php | 21 +++ .../Revisions/RevisionsListAbility.php | 10 +- .../WordPress/Schema/ContentSchema.php | 22 ++- .../WordPress/WordPressIntegration.php | 11 +- src/Plugin.php | 14 +- tests/Support/FakeBuilderDetector.php | 26 ++++ .../Architecture/CoreIndependenceTest.php | 146 ++++++++++++++++++ .../Integrations/Core/CoreIntegrationTest.php | 22 +++ .../Core/EnvironmentProbeTest.php | 61 ++++---- .../Divi/DiviBuilderDetectorTest.php | 32 ++++ .../Divi/DiviEnvironmentComponentTest.php | 68 ++++++++ .../Integrations/Divi/DiviIntegrationTest.php | 43 ++++++ .../Integrations/Divi/DiviLogoUsageTest.php | 67 ++++++++ .../Media/MediaUsageFinderTest.php | 72 ++++++++- .../Content/ContentUpdateAbilityTest.php | 14 +- .../WordPress/Content/PostPresenterTest.php | 60 +++++-- .../Revisions/RevisionsListAbilityTest.php | 12 +- .../WordPress/Schema/ContentSchemaTest.php | 32 +++- 33 files changed, 952 insertions(+), 154 deletions(-) create mode 100644 src/Integrations/Core/EnvironmentComponent.php create mode 100644 src/Integrations/Divi/DiviBuilderDetector.php create mode 100644 src/Integrations/Divi/DiviEnvironmentComponent.php create mode 100644 src/Integrations/Divi/DiviLogoUsage.php create mode 100644 src/Integrations/Media/AttachmentFile.php create mode 100644 src/Integrations/Media/MediaUsageSource.php create mode 100644 src/Integrations/WordPress/BuilderDetector.php create mode 100644 src/Integrations/WordPress/NoBuilderDetector.php create mode 100644 tests/Support/FakeBuilderDetector.php create mode 100644 tests/Unit/Architecture/CoreIndependenceTest.php create mode 100644 tests/Unit/Integrations/Divi/DiviBuilderDetectorTest.php create mode 100644 tests/Unit/Integrations/Divi/DiviEnvironmentComponentTest.php create mode 100644 tests/Unit/Integrations/Divi/DiviLogoUsageTest.php diff --git a/src/Abilities/DiviMarkup.php b/src/Abilities/DiviMarkup.php index 145f4f2..11d8242 100644 --- a/src/Abilities/DiviMarkup.php +++ b/src/Abilities/DiviMarkup.php @@ -19,6 +19,9 @@ defined('ABSPATH') || exit; * form holds a delimiter WordPress' block parser accepts for divi/* blocks ("']); - $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 y']))); - self::assertFalse(PostPresenter::usesDivi(new WP_Post(['post_content' => '']))); + self::assertTrue($presenter->usesDivi(new WP_Post(['post_content' => 'x y']))); + self::assertFalse($presenter->usesDivi(new WP_Post(['post_content' => '']))); + } + + 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' + ); } } diff --git a/tests/Unit/Integrations/WordPress/Revisions/RevisionsListAbilityTest.php b/tests/Unit/Integrations/WordPress/Revisions/RevisionsListAbilityTest.php index 69aac9e..a803782 100644 --- a/tests/Unit/Integrations/WordPress/Revisions/RevisionsListAbilityTest.php +++ b/tests/Unit/Integrations/WordPress/Revisions/RevisionsListAbilityTest.php @@ -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' => '

Normal

']), ]); - $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 diff --git a/tests/Unit/Integrations/WordPress/Schema/ContentSchemaTest.php b/tests/Unit/Integrations/WordPress/Schema/ContentSchemaTest.php index 705d8f9..e0730a2 100644 --- a/tests/Unit/Integrations/WordPress/Schema/ContentSchemaTest.php +++ b/tests/Unit/Integrations/WordPress/Schema/ContentSchemaTest.php @@ -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')); + } }