From 3d96a4136649f6642dfab73ba9cebc41ee782c29 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sat, 26 Sep 2026 17:47:11 +0200 Subject: [PATCH] test: shared Divi fixtures in tests/Support (T1) - DiviFixtures: markup(), givenPost(), schema(), designSystem(), catalog(), dynamicContentRegistry(), maliciousRefs() - moved verbatim out of DiviPageLoaderTest, DesignSystemTest, ModuleCatalogTest, DynamicContentMapTest and VariableRefTest (its malicious() provider now returns maliciousRefs()). - ComposeFixture moves to tests/Support. - DiviWriterFixture: modules(), designReader(), composition(), a recording saver(), log() and writer(&$saved, &$failures, unfiltered, loader) for the five tests that wired the Divi write stack by hand. No test cases or assertions changed (1397 / 5834). Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Compose => Support}/ComposeFixture.php | 16 +- tests/Support/DiviFixtures.php | 231 ++++++++++++++++++ tests/Support/DiviWriterFixture.php | 117 +++++++++ .../Divi/Compose/AttrWriterTest.php | 7 +- .../Divi/Compose/ComposerTest.php | 8 +- .../Divi/Compose/PlaceholdersTest.php | 1 + .../Divi/Compose/ShortformWriterTest.php | 4 +- .../Divi/Compose/StructureRulesTest.php | 1 + .../Divi/Compose/TreeEditorTest.php | 1 + .../Divi/Compose/VariableRefTest.php | 65 +---- .../Divi/Compose/WarningsTest.php | 7 +- .../Divi/Design/DesignSystemTest.php | 38 +-- .../Design/DiviDesignSystemAbilityTest.php | 3 +- .../Divi/Pages/DiviCompositionTest.php | 11 +- .../Divi/Pages/DiviContentComposerTest.php | 22 +- .../Divi/Pages/DiviPageLoaderTest.php | 64 ++--- .../Divi/Pages/DiviReadAbilitiesTest.php | 36 +-- .../Divi/Pages/DiviReplaceTextAbilityTest.php | 36 +-- .../Divi/Pages/DiviWriteAbilitiesTest.php | 51 +--- .../Divi/Pages/DiviWriterTest.php | 58 ++--- .../Divi/Pages/DynamicContentMapTest.php | 71 +----- .../Divi/Schema/DiviCatalogAbilityTest.php | 3 +- .../Divi/Schema/ModuleCatalogTest.php | 27 +- .../Content/ContentCreateDiviTest.php | 15 +- 24 files changed, 489 insertions(+), 404 deletions(-) rename tests/{Unit/Integrations/Divi/Compose => Support}/ComposeFixture.php (91%) create mode 100644 tests/Support/DiviFixtures.php create mode 100644 tests/Support/DiviWriterFixture.php diff --git a/tests/Unit/Integrations/Divi/Compose/ComposeFixture.php b/tests/Support/ComposeFixture.php similarity index 91% rename from tests/Unit/Integrations/Divi/Compose/ComposeFixture.php rename to tests/Support/ComposeFixture.php index 7ddce21..6a0439e 100644 --- a/tests/Unit/Integrations/Divi/Compose/ComposeFixture.php +++ b/tests/Support/ComposeFixture.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose; +namespace LIT\MCP\Tests\Support; use LIT\MCP\Integrations\Divi\Compose\AttrWriter; use LIT\MCP\Integrations\Divi\Compose\Composer; @@ -12,8 +12,6 @@ use LIT\MCP\Integrations\Divi\Compose\ShortformWriter; use LIT\MCP\Integrations\Divi\Compose\StructureRules; use LIT\MCP\Integrations\Divi\Compose\ValueSanitizer; use LIT\MCP\Integrations\Divi\Design\DesignSystem; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; -use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest; /** * Compose classes wired with the real Divi 5.2.1 sample catalog, the sample design system and @@ -130,12 +128,12 @@ final class ComposeFixture public static function placeholders(): Placeholders { - return new Placeholders(DesignSystemTest::sample()); + return new Placeholders(DiviFixtures::designSystem()); } public static function attrWriter(bool $unfiltered = false): AttrWriter { - return new AttrWriter(ModuleCatalogTest::sampleCatalog(), self::placeholders(), self::sanitizer($unfiltered)); + return new AttrWriter(DiviFixtures::catalog(), self::placeholders(), self::sanitizer($unfiltered)); } /** @@ -149,7 +147,7 @@ final class ComposeFixture public static function rules(): StructureRules { - return new StructureRules(ModuleCatalogTest::sampleCatalog()); + return new StructureRules(DiviFixtures::catalog()); } /** @@ -158,12 +156,12 @@ final class ComposeFixture */ public static function composer(bool $unfiltered = false, ?DesignSystem $design = null): Composer { - $design ??= DesignSystemTest::sample(); + $design ??= DiviFixtures::designSystem(); $placeholders = new Placeholders($design); - $attrWriter = new AttrWriter(ModuleCatalogTest::sampleCatalog(), $placeholders, self::sanitizer($unfiltered)); + $attrWriter = new AttrWriter(DiviFixtures::catalog(), $placeholders, self::sanitizer($unfiltered)); return new Composer( - ModuleCatalogTest::sampleCatalog(), + DiviFixtures::catalog(), self::rules(), new ShortformWriter($placeholders, self::media(), $attrWriter), $attrWriter, diff --git a/tests/Support/DiviFixtures.php b/tests/Support/DiviFixtures.php new file mode 100644 index 0000000..78f3d9e --- /dev/null +++ b/tests/Support/DiviFixtures.php @@ -0,0 +1,231 @@ + 7, 'post_type' => 'page', 'post_status' => 'draft', 'post_title' => 'Start', 'post_content' => $content], + $fields + )); + Functions\when('get_post')->justReturn($post); + Functions\when('current_user_can')->justReturn(true); + Functions\when('get_permalink')->justReturn('https://kunde.test/?page_id=7'); + Functions\when('add_query_arg')->alias(static fn (string $key, string $value, string $url): string => $url . '&' . $key . '=' . $value); + + return $post; + } + + public static function schema(bool $diviEnabled = true): ContentSchema + { + $schema = Mockery::mock(ContentSchema::class); + $schema->allows('isManaged')->andReturn(true); + $schema->allows('diviEnabled')->andReturn($diviEnabled); + + return $schema; + } + + /** Shapes as GlobalData / GlobalPreset return them (Divi 5.2.1, verified in DDEV). */ + public static function designSystem(): DesignSystem + { + return DesignSystem::fromDivi( + [ + 'gcid-primary-color' => ['color' => '#2ea3f2', 'label' => 'Primärfarbe', 'status' => 'active'], + 'gcid-body-color' => ['color' => '#666666', 'label' => 'Fließtextfarbe', 'status' => 'active'], + 'gcid-a1b2' => ['color' => '#0b3d59', 'label' => 'Nachtblau', 'status' => 'active'], + 'gcid-old' => ['color' => '#ff0000', 'label' => 'Alt', 'status' => 'inactive'], + ], + [ + 'numbers' => ['gvid-r1' => ['id' => 'gvid-r1', 'label' => 'Radius', 'value' => '8px', 'status' => 'active']], + 'strings' => ['gvid-s1' => ['id' => 'gvid-s1', 'label' => 'Telefon', 'value' => '0221 123', 'status' => 'active']], + // Divi returns every variable type as stdClass (verified in DDEV). + 'fonts' => (object) [ + '--et_global_heading_font' => ['id' => '--et_global_heading_font', 'label' => 'Überschrift', 'value' => 'Open Sans', 'status' => 'active'], + '--et_global_body_font' => ['id' => '--et_global_body_font', 'label' => 'Fließtext', 'value' => 'Lato', 'status' => 'active'], + ], + 'links' => new \stdClass(), + ], + [ + 'module' => ['divi/button' => ['default' => 'p-1', 'items' => [ + 'p-1' => ['id' => 'p-1', 'name' => 'Standard', 'moduleName' => 'divi/button'], + 'p-2' => ['id' => 'p-2', 'name' => 'CTA primär', 'moduleName' => 'divi/button'], + ]]], + 'group' => ['divi/font' => ['default' => '', 'items' => [ + 'g-1' => ['id' => 'g-1', 'name' => 'Fließtext groß', 'groupName' => 'divi/font'], + ]]], + ], + ['body_font_size' => '16'] + ); + } + + /** Real Divi 5.2.1 metadata (tests/Integration/divi-metadata-fixture.php). */ + public static function catalog(): ModuleCatalog + { + $sample = json_decode( + (string) file_get_contents(dirname(__DIR__) . '/Fixtures/divi/metadata-sample.json'), + true + ); + + return new ModuleCatalog( + $sample['metadata'], + static fn (string $name): array => (array) ($sample['defaults'][$name] ?? []) + ); + } + + /** + * Shape of DynamicContentOptions::get_options($pageId, 'edit') in Divi 5.2.1 (excerpt of a + * live dump for a page: no post_categories/post_tags – pages have no such taxonomies). + * + * @return array> + */ + public static function dynamicContentRegistry(): array + { + $text = static fn (): array => ['label' => 'x', 'type' => 'text', 'default' => '']; + $option = static fn (string $id, string $group, array $fields): array => [ + 'id' => $id, 'label' => $id, 'type' => 'text', 'custom' => false, 'group' => $group, 'fields' => $fields, + ]; + + return [ + 'post_title' => $option('post_title', 'Default', ['before' => $text(), 'after' => $text()]), + 'post_excerpt' => $option('post_excerpt', 'Default', [ + 'before' => $text(), 'after' => $text(), 'words' => $text(), 'read_more_label' => $text(), + ]), + 'post_date' => $option('post_date', 'Default', [ + 'before' => $text(), + 'after' => $text(), + 'date_format' => ['label' => 'x', 'type' => 'select', 'default' => 'default'], + 'custom_date_format' => $text(), + ]), + 'post_author' => $option('post_author', 'Default', [ + 'before' => $text(), + 'after' => $text(), + 'name_format' => ['type' => 'select'], + 'link' => ['type' => 'yes_no_button'], + ]), + 'site_title' => $option('site_title', 'Default', ['before' => $text(), 'after' => $text()]), + 'post_link_url' => $option('post_link_url', 'Default', []), + 'post_link_url_page' => $option('post_link_url_page', 'Default', ['post_id' => ['type' => 'select_post']]), + 'post_featured_image' => $option('post_featured_image', 'Default', []), + 'post_meta_key' => $option('post_meta_key', 'Standard', [ + 'before' => $text(), + 'after' => $text(), + 'select_meta_key' => ['type' => 'select'], + 'meta_key' => $text(), + 'enable_html' => ['type' => 'yes_no_button'], + ]), + 'loop_post_title' => $option('loop_post_title', 'Loop', [ + 'before' => $text(), 'after' => $text(), 'loop_position' => $text(), + ]), + 'loop_post_meta_key_manual_custom_field' => $option('loop_post_meta_key_manual_custom_field', 'Loop', [ + 'loop_meta_key' => $text(), + ]), + // Odd entries a third-party filter could add. + 'broken_fields' => $option('broken_fields', 'Default', ['a' => 'not-an-array', 'b' => ['type' => 3]]), + 'no_fields' => ['id' => 'no_fields', 'type' => 'text'], + 7 => $option('7', 'Default', []), + 'not_an_array' => 'x', + ]; + } + + /** + * Hand-crafted attacks (preflight §4 "Malicious $variable tests"). + * + * @return array Value => part of the German error. + */ + public static function maliciousRefs(): array + { + $end = self::escape('0029') . '$'; + + return [ + 'escaped end marker in before' => [self::content('post_title', '{"before":"x' . $end . ' y","after":""}'), 'allein im Wert'], + 'escaped end marker in after' => [self::content('post_title', '{"before":"","after":"' . $end . '"}'), 'allein im Wert'], + 'escaped end marker in date format' => [self::content('post_date', '{"custom_date_format":"d' . $end . '"}'), 'allein im Wert'], + 'escaped end marker in name' => [self::content('post_title' . $end, '{}'), 'Unbekannter Name'], + 'select with markup' => [self::content('post_date', '{"date_format":"d"}'), 'date_format'], + 'type evil' => ['$variable({"type":"evil","value":{"name":"gcid-a1b2","settings":{}}})$', 'Typ'], + 'unknown gcid' => ['$variable({"type":"color","value":{"name":"gcid-nope","settings":{}}})$', 'gcid-nope'], + 'unknown gvid' => [self::content('gvid-nope', '{}'), 'gvid-nope'], + 'content gvid as color' => ['$variable({"type":"color","value":{"name":"gvid-s1","settings":{}}})$', 'gvid-s1'], + 'color gcid as content' => [self::content('gcid-a1b2', '{}'), 'gcid-a1b2'], + 'name with paren' => ['$variable({"type":"color","value":{"name":"gcid-a1b2)","settings":{}}})$', 'Name'], + 'name with style breakout' => [ + '$variable({"type":"color","value":{"name":"gcid-a1b2;}","settings":{}}})$', + 'Name', + ], + 'settings on a global variable' => [self::content('gvid-s1', '{"before":"x"}'), 'before'], + 'settings on a theme font' => [self::content('--et_global_heading_font', '{"after":""}'), 'after'], + 'unknown settings key' => [self::content('post_title', '{"before":"","foo":"1"}'), 'foo'], + 'setting the option lacks' => [self::content('post_featured_image', '{"before":"x"}'), 'before'], + 'enable_html' => [self::content('custom_meta_telefon', '{"before":"","after":"","enable_html":"on"}'), 'enable_html'], + 'color setting not numeric' => [ + '$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{"opacity":"1;}"}}})$', + 'opacity', + ], + 'protected meta key' => [self::content('custom_meta__edit_lock', '{}'), 'custom_meta__edit_lock'], + 'doubled meta prefix' => [ + self::content('custom_meta_custom_meta__edit_lock', '{}'), + 'custom_meta_custom_meta__edit_lock', + ], + 'loop content outside a loop' => [self::content('loop_post_title', '{}'), 'loop_post_title'], + 'unknown dynamic content' => [self::content('post_meta_key', '{}'), 'post_meta_key'], + 'extra top-level key' => ['$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{}},"x":1})$', 'x'], + 'extra value key' => ['$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{},"x":1}})$', 'x'], + 'greedy smuggling' => ['$variable({"a":"})$"})$', 'allein im Wert'], + 'nested reference in before' => [self::content('post_title', '{"before":"$variable(x)$","after":""}'), 'allein im Wert'], + // S28: a marker the sanitizer rebuilds inside a cleaned setting (html(), text()). + 'tag-split marker in before' => [self::content('post_title', '{"before":"$variable(y","after":""}'), 'allein im Wert'], + 'NUL-split marker in after' => [ + self::content('post_title', '{"before":"","after":"$variable' . self::escape('0000') . '(y"}'), + 'allein im Wert', + ], + 'octet-split marker in date format' => [self::content('post_date', '{"custom_date_format":"$variable%41(y"}'), 'allein im Wert'], + // S32: text() keeps control characters (like sanitize_text_field); VariableRef strips them. + 'NUL-split marker in date format' => [ + self::content('post_date', '{"custom_date_format":"$variable' . self::escape('0000') . '(y"}'), + 'allein im Wert', + ], + 'DEL-split marker in date format' => [ + self::content('post_date', '{"custom_date_format":"$variable' . self::escape('007f') . '(y"}'), + 'allein im Wert', + ], + 'nested marker in before' => [self::content('post_title', '{"before":"$variable(","after":""}'), 'allein im Wert'], + 'broken json' => ['$variable(kaputt)$', 'JSON'], + 'trailing text' => [self::content('post_title', '{}') . ' ', 'allein im Wert'], + 'embedded in text' => ['

Hi ' . self::content('post_title', '{"before":"","after":""}') . '

', 'allein im Wert'], + ]; + } + + private static function content(string $name, string $settings): string + { + return '$variable({"type":"content","value":{"name":"' . $name . '","settings":' . $settings . '}})$'; + } + + /** A JSON "\uXXXX" escape, built from bytes so no tool turns it into the character itself. */ + private static function escape(string $hex): string + { + return chr(92) . 'u' . $hex; + } +} diff --git a/tests/Support/DiviWriterFixture.php b/tests/Support/DiviWriterFixture.php new file mode 100644 index 0000000..93292e1 --- /dev/null +++ b/tests/Support/DiviWriterFixture.php @@ -0,0 +1,117 @@ +allows('catalog')->andReturn(DiviFixtures::catalog()); + $modules->allows('builderVersion')->andReturn('5.2.1'); + + return $modules; + } + + public static function designReader(): DesignSystemReader + { + $design = Mockery::mock(DesignSystemReader::class); + $design->allows('read')->andReturn(DiviFixtures::designSystem()); + + return $design; + } + + /** + * $design is the reader the writer gets too (one instance, like the live wiring); without + * $registry the composition uses DynamicContentMap's default (VariableRef's built-in map in + * unit tests, where Divi's classes are missing). + */ + public static function composition( + DesignSystemReader $design, + bool $unfiltered = false, + ?DynamicContentMap $registry = null + ): DiviComposition { + return new DiviComposition( + self::modules(), + $design, + ComposeFixture::sanitizer($unfiltered), + ComposeFixture::media(), + $registry ?? new DynamicContentMap() + ); + } + + /** + * A DiviSaver whose save() appends the content to $saved, puts it on the post and reports + * revision 811. Each call first takes the next entry of $failures and throws it, if any. + * + * @param list $saved + * @param list $failures + */ + public static function saver(array &$saved, array &$failures = []): DiviSaver + { + $saver = Mockery::mock(DiviSaver::class); + $saver->allows('save')->andReturnUsing(static function (WP_Post $post, string $content) use (&$saved, &$failures): array { + $failure = array_shift($failures); + if ($failure !== null) { + throw $failure; + } + $saved[] = $content; + $post->post_content = $content; + + return ['revision_id' => 811, 'warnings' => []]; + }); + + return $saver; + } + + /** A ChangeLog that accepts every record (log id 4711). */ + public static function log(): ChangeLog + { + $log = Mockery::mock(ChangeLog::class); + $log->allows('record')->andReturn(4711); + + return $log; + } + + /** + * The writer over the page of DiviFixtures::givenPost(), saving into $saved (see saver()). + * + * @param list $saved + * @param list $failures + */ + public static function writer( + array &$saved, + array &$failures = [], + bool $unfiltered = false, + ?DiviPageLoader $loader = null + ): DiviWriter { + $design = self::designReader(); + + return new DiviWriter( + $loader ?? new DiviPageLoader(DiviFixtures::schema()), + self::saver($saved, $failures), + self::log(), + $design, + self::composition($design, $unfiltered) + ); + } +} diff --git a/tests/Unit/Integrations/Divi/Compose/AttrWriterTest.php b/tests/Unit/Integrations/Divi/Compose/AttrWriterTest.php index af367c4..738b029 100644 --- a/tests/Unit/Integrations/Divi/Compose/AttrWriterTest.php +++ b/tests/Unit/Integrations/Divi/Compose/AttrWriterTest.php @@ -6,7 +6,8 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose; use InvalidArgumentException; use LIT\MCP\Integrations\Divi\Compose\AttrWriter; -use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest; +use LIT\MCP\Tests\Support\ComposeFixture; +use LIT\MCP\Tests\Support\DiviFixtures; use PHPUnit\Framework\TestCase; final class AttrWriterTest extends TestCase @@ -156,7 +157,7 @@ final class AttrWriterTest extends TestCase public static function malicious(): array { $cases = []; - foreach (VariableRefTest::malicious() as $name => [$value]) { + foreach (DiviFixtures::maliciousRefs() as $name => [$value]) { $cases[$name] = ['divi/text', 'content.innerContent', ['desktop' => $value], self::TEXT]; } @@ -317,7 +318,7 @@ final class AttrWriterTest extends TestCase /** Ruling S23: content.innerContent of unknown kind (defined only in Divi's JS) is rich text. */ public function testUnknownContentInnerContentIsRichText(): void { - self::assertSame('unknown', ModuleCatalogTest::sampleCatalog()->pathKind('divi/blurb', 'content.innerContent')); + self::assertSame('unknown', DiviFixtures::catalog()->pathKind('divi/blurb', 'content.innerContent')); self::assertSame('

Hallo

', ComposeFixture::attrWriter()->cleanValue('divi/blurb', 'content.innerContent', '

Hallo

')); // Other unknown innerContent stays text. self::assertSame('Los', ComposeFixture::attrWriter()->cleanValue('divi/button', 'button.innerContent', 'Los')); diff --git a/tests/Unit/Integrations/Divi/Compose/ComposerTest.php b/tests/Unit/Integrations/Divi/Compose/ComposerTest.php index 22d6174..8256e0e 100644 --- a/tests/Unit/Integrations/Divi/Compose/ComposerTest.php +++ b/tests/Unit/Integrations/Divi/Compose/ComposerTest.php @@ -11,8 +11,8 @@ use LIT\MCP\Integrations\Divi\BlockTree\Parser; use LIT\MCP\Integrations\Divi\BlockTree\Serializer; use LIT\MCP\Integrations\Divi\Compose\Composer; use LIT\MCP\Integrations\Divi\Design\DesignSystem; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; -use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest; +use LIT\MCP\Tests\Support\ComposeFixture; +use LIT\MCP\Tests\Support\DiviFixtures; use PHPUnit\Framework\TestCase; final class ComposerTest extends TestCase @@ -442,11 +442,11 @@ final class ComposerTest extends TestCase $this->expectException(InvalidArgumentException::class); new Composer( - ModuleCatalogTest::sampleCatalog(), + DiviFixtures::catalog(), ComposeFixture::rules(), ComposeFixture::shortform(), ComposeFixture::attrWriter(), - DesignSystemTest::sample(), + DiviFixtures::designSystem(), '5.2.1"},"x":{"' ); } diff --git a/tests/Unit/Integrations/Divi/Compose/PlaceholdersTest.php b/tests/Unit/Integrations/Divi/Compose/PlaceholdersTest.php index 1cfbc4a..c773112 100644 --- a/tests/Unit/Integrations/Divi/Compose/PlaceholdersTest.php +++ b/tests/Unit/Integrations/Divi/Compose/PlaceholdersTest.php @@ -7,6 +7,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose; use InvalidArgumentException; use LIT\MCP\Integrations\Divi\Compose\Placeholders; use LIT\MCP\Integrations\Divi\Design\DesignSystem; +use LIT\MCP\Tests\Support\ComposeFixture; use PHPUnit\Framework\TestCase; final class PlaceholdersTest extends TestCase diff --git a/tests/Unit/Integrations/Divi/Compose/ShortformWriterTest.php b/tests/Unit/Integrations/Divi/Compose/ShortformWriterTest.php index 5849acc..4ca6441 100644 --- a/tests/Unit/Integrations/Divi/Compose/ShortformWriterTest.php +++ b/tests/Unit/Integrations/Divi/Compose/ShortformWriterTest.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose; use InvalidArgumentException; +use LIT\MCP\Tests\Support\ComposeFixture; +use LIT\MCP\Tests\Support\DiviFixtures; use PHPUnit\Framework\TestCase; final class ShortformWriterTest extends TestCase @@ -146,7 +148,7 @@ final class ShortformWriterTest extends TestCase public static function malicious(): array { $rows = []; - foreach (VariableRefTest::malicious() as $name => [$value]) { + foreach (DiviFixtures::maliciousRefs() as $name => [$value]) { foreach (array_keys(self::TARGETS) as $kind) { $rows[$kind . ': ' . $name] = [$kind, $value, $kind !== 'text']; } diff --git a/tests/Unit/Integrations/Divi/Compose/StructureRulesTest.php b/tests/Unit/Integrations/Divi/Compose/StructureRulesTest.php index 4513345..50d0fea 100644 --- a/tests/Unit/Integrations/Divi/Compose/StructureRulesTest.php +++ b/tests/Unit/Integrations/Divi/Compose/StructureRulesTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose; use LIT\MCP\Integrations\Divi\BlockTree\Block; +use LIT\MCP\Tests\Support\ComposeFixture; use PHPUnit\Framework\TestCase; final class StructureRulesTest extends TestCase diff --git a/tests/Unit/Integrations/Divi/Compose/TreeEditorTest.php b/tests/Unit/Integrations/Divi/Compose/TreeEditorTest.php index d2e12d7..d7af5b3 100644 --- a/tests/Unit/Integrations/Divi/Compose/TreeEditorTest.php +++ b/tests/Unit/Integrations/Divi/Compose/TreeEditorTest.php @@ -10,6 +10,7 @@ use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex; use LIT\MCP\Integrations\Divi\BlockTree\Parser; use LIT\MCP\Integrations\Divi\BlockTree\Serializer; use LIT\MCP\Integrations\Divi\Compose\TreeEditor; +use LIT\MCP\Tests\Support\ComposeFixture; use PHPUnit\Framework\TestCase; final class TreeEditorTest extends TestCase diff --git a/tests/Unit/Integrations/Divi/Compose/VariableRefTest.php b/tests/Unit/Integrations/Divi/Compose/VariableRefTest.php index 30835ae..452ac83 100644 --- a/tests/Unit/Integrations/Divi/Compose/VariableRefTest.php +++ b/tests/Unit/Integrations/Divi/Compose/VariableRefTest.php @@ -6,7 +6,8 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose; use InvalidArgumentException; use LIT\MCP\Integrations\Divi\Compose\VariableRef; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; +use LIT\MCP\Tests\Support\ComposeFixture; +use LIT\MCP\Tests\Support\DiviFixtures; use PHPUnit\Framework\TestCase; /** Ruling S1/S2/S12/S21/S22: every $variable(…)$ value is parsed, strictly checked, cleaned and re-serialized. */ @@ -15,7 +16,7 @@ final class VariableRefTest extends TestCase /** @param array>|null $dynamicContent */ private static function ref(?array $dynamicContent = null): VariableRef { - return new VariableRef(DesignSystemTest::sample(), $dynamicContent); + return new VariableRef(DiviFixtures::designSystem(), $dynamicContent); } private static function content(string $name, string $settings): string @@ -125,65 +126,7 @@ final class VariableRefTest extends TestCase */ public static function malicious(): array { - $end = self::escape('0029') . '$'; - - return [ - 'escaped end marker in before' => [self::content('post_title', '{"before":"x' . $end . ' y","after":""}'), 'allein im Wert'], - 'escaped end marker in after' => [self::content('post_title', '{"before":"","after":"' . $end . '"}'), 'allein im Wert'], - 'escaped end marker in date format' => [self::content('post_date', '{"custom_date_format":"d' . $end . '"}'), 'allein im Wert'], - 'escaped end marker in name' => [self::content('post_title' . $end, '{}'), 'Unbekannter Name'], - 'select with markup' => [self::content('post_date', '{"date_format":"d"}'), 'date_format'], - 'type evil' => ['$variable({"type":"evil","value":{"name":"gcid-a1b2","settings":{}}})$', 'Typ'], - 'unknown gcid' => ['$variable({"type":"color","value":{"name":"gcid-nope","settings":{}}})$', 'gcid-nope'], - 'unknown gvid' => [self::content('gvid-nope', '{}'), 'gvid-nope'], - 'content gvid as color' => ['$variable({"type":"color","value":{"name":"gvid-s1","settings":{}}})$', 'gvid-s1'], - 'color gcid as content' => [self::content('gcid-a1b2', '{}'), 'gcid-a1b2'], - 'name with paren' => ['$variable({"type":"color","value":{"name":"gcid-a1b2)","settings":{}}})$', 'Name'], - 'name with style breakout' => [ - '$variable({"type":"color","value":{"name":"gcid-a1b2;}","settings":{}}})$', - 'Name', - ], - 'settings on a global variable' => [self::content('gvid-s1', '{"before":"x"}'), 'before'], - 'settings on a theme font' => [self::content('--et_global_heading_font', '{"after":""}'), 'after'], - 'unknown settings key' => [self::content('post_title', '{"before":"","foo":"1"}'), 'foo'], - 'setting the option lacks' => [self::content('post_featured_image', '{"before":"x"}'), 'before'], - 'enable_html' => [self::content('custom_meta_telefon', '{"before":"","after":"","enable_html":"on"}'), 'enable_html'], - 'color setting not numeric' => [ - '$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{"opacity":"1;}"}}})$', - 'opacity', - ], - 'protected meta key' => [self::content('custom_meta__edit_lock', '{}'), 'custom_meta__edit_lock'], - 'doubled meta prefix' => [ - self::content('custom_meta_custom_meta__edit_lock', '{}'), - 'custom_meta_custom_meta__edit_lock', - ], - 'loop content outside a loop' => [self::content('loop_post_title', '{}'), 'loop_post_title'], - 'unknown dynamic content' => [self::content('post_meta_key', '{}'), 'post_meta_key'], - 'extra top-level key' => ['$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{}},"x":1})$', 'x'], - 'extra value key' => ['$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{},"x":1}})$', 'x'], - 'greedy smuggling' => ['$variable({"a":"})$"})$', 'allein im Wert'], - 'nested reference in before' => [self::content('post_title', '{"before":"$variable(x)$","after":""}'), 'allein im Wert'], - // S28: a marker the sanitizer rebuilds inside a cleaned setting (html(), text()). - 'tag-split marker in before' => [self::content('post_title', '{"before":"$variable(y","after":""}'), 'allein im Wert'], - 'NUL-split marker in after' => [ - self::content('post_title', '{"before":"","after":"$variable' . self::escape('0000') . '(y"}'), - 'allein im Wert', - ], - 'octet-split marker in date format' => [self::content('post_date', '{"custom_date_format":"$variable%41(y"}'), 'allein im Wert'], - // S32: text() keeps control characters (like sanitize_text_field); VariableRef strips them. - 'NUL-split marker in date format' => [ - self::content('post_date', '{"custom_date_format":"$variable' . self::escape('0000') . '(y"}'), - 'allein im Wert', - ], - 'DEL-split marker in date format' => [ - self::content('post_date', '{"custom_date_format":"$variable' . self::escape('007f') . '(y"}'), - 'allein im Wert', - ], - 'nested marker in before' => [self::content('post_title', '{"before":"$variable(","after":""}'), 'allein im Wert'], - 'broken json' => ['$variable(kaputt)$', 'JSON'], - 'trailing text' => [self::content('post_title', '{}') . ' ', 'allein im Wert'], - 'embedded in text' => ['

Hi ' . self::content('post_title', '{"before":"","after":""}') . '

', 'allein im Wert'], - ]; + return DiviFixtures::maliciousRefs(); } /** @dataProvider malicious */ diff --git a/tests/Unit/Integrations/Divi/Compose/WarningsTest.php b/tests/Unit/Integrations/Divi/Compose/WarningsTest.php index 898c05e..f73ebad 100644 --- a/tests/Unit/Integrations/Divi/Compose/WarningsTest.php +++ b/tests/Unit/Integrations/Divi/Compose/WarningsTest.php @@ -7,7 +7,8 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose; use LIT\MCP\Integrations\Divi\BlockTree\Document; use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex; use LIT\MCP\Integrations\Divi\Compose\Warnings; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; +use LIT\MCP\Tests\Support\ComposeFixture; +use LIT\MCP\Tests\Support\DiviFixtures; use PHPUnit\Framework\TestCase; final class WarningsTest extends TestCase @@ -34,7 +35,7 @@ final class WarningsTest extends TestCase $document->append($block); } - $warnings = (new Warnings(DesignSystemTest::sample()))->collect(NodeIndex::build($document), $composed['blocks']); + $warnings = (new Warnings(DiviFixtures::designSystem()))->collect(NodeIndex::build($document), $composed['blocks']); self::assertSame( [ @@ -50,6 +51,6 @@ final class WarningsTest extends TestCase $warnings ); - self::assertSame([], (new Warnings(DesignSystemTest::sample()))->collect(NodeIndex::build($document), [])); + self::assertSame([], (new Warnings(DiviFixtures::designSystem()))->collect(NodeIndex::build($document), [])); } } diff --git a/tests/Unit/Integrations/Divi/Design/DesignSystemTest.php b/tests/Unit/Integrations/Divi/Design/DesignSystemTest.php index c1d9a9b..906d27d 100644 --- a/tests/Unit/Integrations/Divi/Design/DesignSystemTest.php +++ b/tests/Unit/Integrations/Divi/Design/DesignSystemTest.php @@ -5,46 +5,14 @@ declare(strict_types=1); namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design; use LIT\MCP\Integrations\Divi\Design\DesignSystem; +use LIT\MCP\Tests\Support\DiviFixtures; use PHPUnit\Framework\TestCase; final class DesignSystemTest extends TestCase { - /** Shapes as GlobalData / GlobalPreset return them (Divi 5.2.1, verified in DDEV). */ - public static function sample(): DesignSystem - { - return DesignSystem::fromDivi( - [ - 'gcid-primary-color' => ['color' => '#2ea3f2', 'label' => 'Primärfarbe', 'status' => 'active'], - 'gcid-body-color' => ['color' => '#666666', 'label' => 'Fließtextfarbe', 'status' => 'active'], - 'gcid-a1b2' => ['color' => '#0b3d59', 'label' => 'Nachtblau', 'status' => 'active'], - 'gcid-old' => ['color' => '#ff0000', 'label' => 'Alt', 'status' => 'inactive'], - ], - [ - 'numbers' => ['gvid-r1' => ['id' => 'gvid-r1', 'label' => 'Radius', 'value' => '8px', 'status' => 'active']], - 'strings' => ['gvid-s1' => ['id' => 'gvid-s1', 'label' => 'Telefon', 'value' => '0221 123', 'status' => 'active']], - // Divi returns every variable type as stdClass (verified in DDEV). - 'fonts' => (object) [ - '--et_global_heading_font' => ['id' => '--et_global_heading_font', 'label' => 'Überschrift', 'value' => 'Open Sans', 'status' => 'active'], - '--et_global_body_font' => ['id' => '--et_global_body_font', 'label' => 'Fließtext', 'value' => 'Lato', 'status' => 'active'], - ], - 'links' => new \stdClass(), - ], - [ - 'module' => ['divi/button' => ['default' => 'p-1', 'items' => [ - 'p-1' => ['id' => 'p-1', 'name' => 'Standard', 'moduleName' => 'divi/button'], - 'p-2' => ['id' => 'p-2', 'name' => 'CTA primär', 'moduleName' => 'divi/button'], - ]]], - 'group' => ['divi/font' => ['default' => '', 'items' => [ - 'g-1' => ['id' => 'g-1', 'name' => 'Fließtext groß', 'groupName' => 'divi/font'], - ]]], - ], - ['body_font_size' => '16'] - ); - } - public function testReadsColorsVariablesFontsSizesAndPresets(): void { - $design = self::sample(); + $design = DiviFixtures::designSystem(); self::assertSame( ['id' => 'gcid-a1b2', 'label' => 'Nachtblau', 'value' => '#0b3d59'], @@ -68,7 +36,7 @@ final class DesignSystemTest extends TestCase public function testLooksUpByAliasLabelOrId(): void { - $design = self::sample(); + $design = DiviFixtures::designSystem(); self::assertSame('gcid-primary-color', $design->colorId('primary')); self::assertSame('gcid-a1b2', $design->colorId('nachtblau')); diff --git a/tests/Unit/Integrations/Divi/Design/DiviDesignSystemAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviDesignSystemAbilityTest.php index a26ed17..3a5da0b 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviDesignSystemAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviDesignSystemAbilityTest.php @@ -7,6 +7,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design; use Brain\Monkey\Functions; use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; use LIT\MCP\Integrations\Divi\Design\DiviDesignSystemAbility; +use LIT\MCP\Tests\Support\DiviFixtures; use LIT\MCP\Tests\Unit\TestCase; use Mockery; @@ -15,7 +16,7 @@ final class DiviDesignSystemAbilityTest extends TestCase public function testReturnsDesignSystemWithPlaceholders(): void { $reader = Mockery::mock(DesignSystemReader::class); - $reader->allows('read')->andReturn(DesignSystemTest::sample()); + $reader->allows('read')->andReturn(DiviFixtures::designSystem()); Functions\when('current_user_can')->justReturn(true); $ability = new DiviDesignSystemAbility($reader); diff --git a/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php b/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php index 4e6c7c9..3e18367 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php @@ -14,9 +14,8 @@ use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; use LIT\MCP\Integrations\Divi\Pages\DiviComposition; use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap; use LIT\MCP\Integrations\Divi\Schema\DiviModules; -use LIT\MCP\Tests\Unit\Integrations\Divi\Compose\ComposeFixture; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; -use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest; +use LIT\MCP\Tests\Support\ComposeFixture; +use LIT\MCP\Tests\Support\DiviFixtures; use LIT\MCP\Tests\Unit\TestCase; use Mockery; @@ -28,14 +27,14 @@ final class DiviCompositionTest extends TestCase private function composition(): DiviComposition { $modules = Mockery::mock(DiviModules::class); - $modules->allows('catalog')->andReturn(ModuleCatalogTest::sampleCatalog()); + $modules->allows('catalog')->andReturn(DiviFixtures::catalog()); $modules->allows('builderVersion')->andReturn('5.2.1'); $design = Mockery::mock(DesignSystemReader::class); - $design->allows('read')->andReturn(DesignSystemTest::sample()); + $design->allows('read')->andReturn(DiviFixtures::designSystem()); $registry = new DynamicContentMap(function (int $postId): array { $this->registryCalls[] = $postId; - return DynamicContentMapTest::registry(); + return DiviFixtures::dynamicContentRegistry(); }); return new DiviComposition($modules, $design, ComposeFixture::sanitizer(), ComposeFixture::media(), $registry); diff --git a/tests/Unit/Integrations/Divi/Pages/DiviContentComposerTest.php b/tests/Unit/Integrations/Divi/Pages/DiviContentComposerTest.php index 7272a4c..881d169 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviContentComposerTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviContentComposerTest.php @@ -6,15 +6,11 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages; use LIT\MCP\Integrations\Divi\BlockTree\Parser; use LIT\MCP\Integrations\Divi\Compose\VariableRef; -use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; -use LIT\MCP\Integrations\Divi\Pages\DiviComposition; use LIT\MCP\Integrations\Divi\Pages\DiviContentComposer; use LIT\MCP\Integrations\Divi\Pages\DiviSaver; use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap; -use LIT\MCP\Integrations\Divi\Schema\DiviModules; -use LIT\MCP\Tests\Unit\Integrations\Divi\Compose\ComposeFixture; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; -use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest; +use LIT\MCP\Tests\Support\DiviFixtures; +use LIT\MCP\Tests\Support\DiviWriterFixture; use LIT\MCP\Tests\Unit\TestCase; use Mockery; use Mockery\MockInterface; @@ -48,20 +44,16 @@ final class DiviContentComposerTest extends TestCase */ private function composer(bool $diviEnabled = true, ?callable $registry = null): DiviContentComposer { - $modules = Mockery::mock(DiviModules::class); - $modules->allows('catalog')->andReturn(ModuleCatalogTest::sampleCatalog()); - $modules->allows('builderVersion')->andReturn('5.2.1'); - $design = Mockery::mock(DesignSystemReader::class); - $design->allows('read')->andReturn(DesignSystemTest::sample()); + $design = DiviWriterFixture::designReader(); $map = new DynamicContentMap(function (int $postId) use ($registry): array { $this->registryCalls[] = $postId; - return $registry !== null ? $registry($postId) : DynamicContentMapTest::registry(); + return $registry !== null ? $registry($postId) : DiviFixtures::dynamicContentRegistry(); }); return new DiviContentComposer( - DiviPageLoaderTest::schema($diviEnabled), - new DiviComposition($modules, $design, ComposeFixture::sanitizer(), ComposeFixture::media(), $map), + DiviFixtures::schema($diviEnabled), + DiviWriterFixture::composition($design, false, $map), $this->saver, $design ); @@ -124,7 +116,7 @@ final class DiviContentComposerTest extends TestCase public function testWriteRefusesWhatTheNewEntrysMapDoesNotOffer(): void { $registry = static function (int $postId): array { - $options = DynamicContentMapTest::registry(); + $options = DiviFixtures::dynamicContentRegistry(); if ($postId !== 0) { unset($options['site_title']); } diff --git a/tests/Unit/Integrations/Divi/Pages/DiviPageLoaderTest.php b/tests/Unit/Integrations/Divi/Pages/DiviPageLoaderTest.php index 12692ca..1afb1c6 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviPageLoaderTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviPageLoaderTest.php @@ -4,66 +4,34 @@ declare(strict_types=1); namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages; -use Brain\Monkey\Functions; use LIT\MCP\Integrations\Divi\Pages\DiviPage; use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader; -use LIT\MCP\Integrations\WordPress\Schema\ContentSchema; +use LIT\MCP\Tests\Support\DiviFixtures; use LIT\MCP\Tests\Unit\TestCase; -use Mockery; use WP_Error; -use WP_Post; final class DiviPageLoaderTest extends TestCase { - public static function fixture(string $name): string - { - return (string) file_get_contents(dirname(__DIR__, 4) . '/Fixtures/divi/' . $name); - } - - /** Stubs everything PostGuard::editable() needs for a post with $content. */ - public static function givenPost(string $content, array $fields = []): WP_Post - { - $post = new WP_Post(array_merge( - ['ID' => 7, 'post_type' => 'page', 'post_status' => 'draft', 'post_title' => 'Start', 'post_content' => $content], - $fields - )); - Functions\when('get_post')->justReturn($post); - Functions\when('current_user_can')->justReturn(true); - Functions\when('get_permalink')->justReturn('https://kunde.test/?page_id=7'); - Functions\when('add_query_arg')->alias(static fn (string $key, string $value, string $url): string => $url . '&' . $key . '=' . $value); - - return $post; - } - - public static function schema(bool $diviEnabled = true): ContentSchema - { - $schema = Mockery::mock(ContentSchema::class); - $schema->allows('isManaged')->andReturn(true); - $schema->allows('diviEnabled')->andReturn($diviEnabled); - - return $schema; - } - public function testLoadsTreeIndexAndTreeRev(): void { - $content = self::fixture('synthetic-basic.html'); - self::givenPost($content); + $content = DiviFixtures::markup('synthetic-basic.html'); + DiviFixtures::givenPost($content); - $page = (new DiviPageLoader(self::schema()))->load(7, true); + $page = (new DiviPageLoader(DiviFixtures::schema()))->load(7, true); self::assertInstanceOf(DiviPage::class, $page); self::assertSame(substr(hash('sha256', $content), 0, 12), $page->treeRev()); self::assertSame('divi/button', $page->index->find('s0.r0.c1.m2')?->name()); self::assertTrue($page->isDiviLayout()); self::assertSame('https://kunde.test/?page_id=7&et_fb=1', $page->editUrl()); - self::assertNull((new DiviPageLoader(self::schema()))->writable($page)); + self::assertNull((new DiviPageLoader(DiviFixtures::schema()))->writable($page)); } public function testRefusesDivi4Content(): void { - self::givenPost(self::fixture('divi4-shortcodes.html')); + DiviFixtures::givenPost(DiviFixtures::markup('divi4-shortcodes.html')); - $result = (new DiviPageLoader(self::schema()))->load(7, false); + $result = (new DiviPageLoader(DiviFixtures::schema()))->load(7, false); self::assertInstanceOf(WP_Error::class, $result); self::assertSame('lit_unsupported', $result->get_error_code()); @@ -72,12 +40,12 @@ final class DiviPageLoaderTest extends TestCase public function testRefusesMalformedMarkupAndLogsIt(): void { - self::givenPost(''); + DiviFixtures::givenPost(''); // error_log() is a PHP internal and cannot be stubbed; redirect it to a temp file instead. $log = (string) tempnam(sys_get_temp_dir(), 'lit-log'); $previous = ini_set('error_log', $log); try { - $result = (new DiviPageLoader(self::schema()))->load(7, false); + $result = (new DiviPageLoader(DiviFixtures::schema()))->load(7, false); } finally { ini_set('error_log', (string) $previous); } @@ -90,8 +58,8 @@ final class DiviPageLoaderTest extends TestCase public function testTrashedEntriesCanBeReadButNotWritten(): void { - self::givenPost(self::fixture('synthetic-basic.html'), ['post_status' => 'trash']); - $loader = new DiviPageLoader(self::schema()); + DiviFixtures::givenPost(DiviFixtures::markup('synthetic-basic.html'), ['post_status' => 'trash']); + $loader = new DiviPageLoader(DiviFixtures::schema()); self::assertInstanceOf(DiviPage::class, $loader->load(7, false)); self::assertSame('lit_validation_failed', $loader->load(7, true)->get_error_code()); @@ -99,18 +67,18 @@ final class DiviPageLoaderTest extends TestCase public function testWritableChecks(): void { - $loader = new DiviPageLoader(self::schema(false)); - self::givenPost(''); + $loader = new DiviPageLoader(DiviFixtures::schema(false)); + DiviFixtures::givenPost(''); $empty = $loader->load(7, true); self::assertSame('lit_unsupported', $loader->writable($empty)->get_error_code()); - $loader = new DiviPageLoader(self::schema()); - self::givenPost("\n

Gutenberg

\n"); + $loader = new DiviPageLoader(DiviFixtures::schema()); + DiviFixtures::givenPost("\n

Gutenberg

\n"); $gutenberg = $loader->load(7, true); self::assertFalse($gutenberg->isDiviLayout()); self::assertStringContainsString('kein Divi-Layout', $loader->writable($gutenberg)->get_error_message()); - self::givenPost('', ['post_status' => 'future', 'post_date_gmt' => '2020-01-01 00:00:00']); + DiviFixtures::givenPost('', ['post_status' => 'future', 'post_date_gmt' => '2020-01-01 00:00:00']); $missed = $loader->load(7, true); self::assertTrue($missed->isDiviLayout()); self::assertSame('lit_validation_failed', $loader->writable($missed)->get_error_code()); diff --git a/tests/Unit/Integrations/Divi/Pages/DiviReadAbilitiesTest.php b/tests/Unit/Integrations/Divi/Pages/DiviReadAbilitiesTest.php index df93599..c748c83 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviReadAbilitiesTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviReadAbilitiesTest.php @@ -8,7 +8,7 @@ use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; use LIT\MCP\Integrations\Divi\Pages\DiviNodeGetAbility; use LIT\MCP\Integrations\Divi\Pages\DiviOutlineAbility; use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; +use LIT\MCP\Tests\Support\DiviFixtures; use LIT\MCP\Tests\Unit\TestCase; use Mockery; use stdClass; @@ -18,16 +18,16 @@ final class DiviReadAbilitiesTest extends TestCase private function design(): DesignSystemReader { $reader = Mockery::mock(DesignSystemReader::class); - $reader->allows('read')->andReturn(DesignSystemTest::sample()); + $reader->allows('read')->andReturn(DiviFixtures::designSystem()); return $reader; } public function testOutlineListsNodesWithTreeRevAndPostRev(): void { - $content = DiviPageLoaderTest::fixture('synthetic-basic.html'); - DiviPageLoaderTest::givenPost($content); - $ability = new DiviOutlineAbility(new DiviPageLoader(DiviPageLoaderTest::schema()), $this->design()); + $content = DiviFixtures::markup('synthetic-basic.html'); + DiviFixtures::givenPost($content); + $ability = new DiviOutlineAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design()); self::assertTrue($ability->permission(['post_id' => 7])); $result = $ability->execute(['post_id' => 7]); @@ -46,8 +46,8 @@ final class DiviReadAbilitiesTest extends TestCase /** Ruling S18: the "limit" input caps children per parent; "truncated" reports the cut. */ public function testOutlineBreadthLimitTruncatesAndReportsIt(): void { - DiviPageLoaderTest::givenPost(DiviPageLoaderTest::fixture('synthetic-basic.html')); - $ability = new DiviOutlineAbility(new DiviPageLoader(DiviPageLoaderTest::schema()), $this->design()); + DiviFixtures::givenPost(DiviFixtures::markup('synthetic-basic.html')); + $ability = new DiviOutlineAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design()); $result = $ability->execute(['post_id' => 7, 'id' => 's0.r0.c1', 'limit' => 2]); @@ -58,8 +58,8 @@ final class DiviReadAbilitiesTest extends TestCase public function testOutlineOfASubtreeAndUnknownNode(): void { - DiviPageLoaderTest::givenPost(DiviPageLoaderTest::fixture('synthetic-basic.html')); - $ability = new DiviOutlineAbility(new DiviPageLoader(DiviPageLoaderTest::schema()), $this->design()); + DiviFixtures::givenPost(DiviFixtures::markup('synthetic-basic.html')); + $ability = new DiviOutlineAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design()); $result = $ability->execute(['post_id' => 7, 'id' => 's1', 'depth' => 1]); self::assertSame(['s1', 's1.r0'], array_column($result['nodes'], 'id')); @@ -71,8 +71,8 @@ final class DiviReadAbilitiesTest extends TestCase public function testOutlineValidatesTheSubtreeId(): void { - DiviPageLoaderTest::givenPost(DiviPageLoaderTest::fixture('synthetic-basic.html')); - $ability = new DiviOutlineAbility(new DiviPageLoader(DiviPageLoaderTest::schema()), $this->design()); + DiviFixtures::givenPost(DiviFixtures::markup('synthetic-basic.html')); + $ability = new DiviOutlineAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design()); self::assertSame( 'lit_validation_failed', @@ -82,11 +82,11 @@ final class DiviReadAbilitiesTest extends TestCase public function testOutlineHintsForEmptyAndReadOnlyContent(): void { - DiviPageLoaderTest::givenPost(''); - $ability = new DiviOutlineAbility(new DiviPageLoader(DiviPageLoaderTest::schema()), $this->design()); + DiviFixtures::givenPost(''); + $ability = new DiviOutlineAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design()); self::assertStringContainsString('lit-divi-insert', $ability->execute(['post_id' => 7])['hints'][0]); - DiviPageLoaderTest::givenPost(DiviPageLoaderTest::fixture('synthetic-mixed.html')); + DiviFixtures::givenPost(DiviFixtures::markup('synthetic-mixed.html')); $hints = $ability->execute(['post_id' => 7])['hints']; self::assertStringContainsString('nur lesbar', $hints[0]); self::assertStringContainsString('nur als Ganzes', $hints[0]); @@ -94,8 +94,8 @@ final class DiviReadAbilitiesTest extends TestCase public function testNodeGetReturnsFullFormShortformAndPresets(): void { - DiviPageLoaderTest::givenPost(DiviPageLoaderTest::fixture('synthetic-basic.html')); - $ability = new DiviNodeGetAbility(new DiviPageLoader(DiviPageLoaderTest::schema()), $this->design()); + DiviFixtures::givenPost(DiviFixtures::markup('synthetic-basic.html')); + $ability = new DiviNodeGetAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design()); $heading = $ability->execute(['post_id' => 7, 'id' => 's0.r0.c1.m0']); @@ -117,8 +117,8 @@ final class DiviReadAbilitiesTest extends TestCase public function testNodeGetValidatesTheId(): void { - DiviPageLoaderTest::givenPost(DiviPageLoaderTest::fixture('synthetic-basic.html')); - $ability = new DiviNodeGetAbility(new DiviPageLoader(DiviPageLoaderTest::schema()), $this->design()); + DiviFixtures::givenPost(DiviFixtures::markup('synthetic-basic.html')); + $ability = new DiviNodeGetAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design()); self::assertSame('lit_validation_failed', $ability->execute(['post_id' => 7, 'id' => 'hero'])->get_error_code()); self::assertSame('lit_not_found', $ability->execute(['post_id' => 7, 'id' => 's5.r0'])->get_error_code()); diff --git a/tests/Unit/Integrations/Divi/Pages/DiviReplaceTextAbilityTest.php b/tests/Unit/Integrations/Divi/Pages/DiviReplaceTextAbilityTest.php index fd960cd..ba92f14 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviReplaceTextAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviReplaceTextAbilityTest.php @@ -7,21 +7,14 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages; use Brain\Monkey\Functions; use LIT\MCP\Abilities\Rev; use LIT\MCP\Abilities\WriteFailed; -use LIT\MCP\ChangeLog\ChangeLog; use LIT\MCP\Integrations\Divi\BlockTree\AttrMap; use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex; use LIT\MCP\Integrations\Divi\BlockTree\Parser; use LIT\MCP\Integrations\Divi\BlockTree\Serializer; -use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; -use LIT\MCP\Integrations\Divi\Pages\DiviComposition; use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader; use LIT\MCP\Integrations\Divi\Pages\DiviReplaceTextAbility; -use LIT\MCP\Integrations\Divi\Pages\DiviSaver; -use LIT\MCP\Integrations\Divi\Pages\DiviWriter; -use LIT\MCP\Integrations\Divi\Schema\DiviModules; -use LIT\MCP\Tests\Unit\Integrations\Divi\Compose\ComposeFixture; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; -use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest; +use LIT\MCP\Tests\Support\DiviFixtures; +use LIT\MCP\Tests\Support\DiviWriterFixture; use LIT\MCP\Tests\Unit\TestCase; use Mockery; use RuntimeException; @@ -47,29 +40,10 @@ final class DiviReplaceTextAbilityTest extends TestCase /** @param list $ids Rows of the prefilter query. */ private function ability(?string $content = null, bool $unfiltered = false, array $ids = [7]): DiviReplaceTextAbility { - $this->post = DiviPageLoaderTest::givenPost($content ?? DiviPageLoaderTest::fixture('synthetic-basic.html')); + $this->post = DiviFixtures::givenPost($content ?? DiviFixtures::markup('synthetic-basic.html')); Functions\when('get_post_type_object')->justReturn((object) ['cap' => (object) ['edit_posts' => 'edit_pages', 'edit_others_posts' => 'edit_others_pages']]); Functions\when('get_preview_post_link')->justReturn('https://kunde.test/?page_id=7&preview=true'); - $schema = DiviPageLoaderTest::schema(); - $modules = Mockery::mock(DiviModules::class); - $modules->allows('catalog')->andReturn(ModuleCatalogTest::sampleCatalog()); - $modules->allows('builderVersion')->andReturn('5.2.1'); - $design = Mockery::mock(DesignSystemReader::class); - $design->allows('read')->andReturn(DesignSystemTest::sample()); - $composition = new DiviComposition($modules, $design, ComposeFixture::sanitizer($unfiltered), ComposeFixture::media()); - $saver = Mockery::mock(DiviSaver::class); - $saver->allows('save')->andReturnUsing(function (WP_Post $post, string $content): array { - $failure = array_shift($this->failures); - if ($failure !== null) { - throw $failure; - } - $this->saved[] = $content; - $post->post_content = $content; - - return ['revision_id' => 811, 'warnings' => []]; - }); - $log = Mockery::mock(ChangeLog::class); - $log->allows('record')->andReturn(4711); + $schema = DiviFixtures::schema(); $loader = new DiviPageLoader($schema); $db = Mockery::mock(\wpdb::class)->makePartial(); $db->allows('get_col')->andReturnUsing(function (string $sql) use ($ids): array { @@ -81,7 +55,7 @@ final class DiviReplaceTextAbilityTest extends TestCase return new DiviReplaceTextAbility( $schema, $loader, - new DiviWriter($loader, $saver, $log, $design, $composition), + DiviWriterFixture::writer($this->saved, $this->failures, $unfiltered, $loader), $db ); } diff --git a/tests/Unit/Integrations/Divi/Pages/DiviWriteAbilitiesTest.php b/tests/Unit/Integrations/Divi/Pages/DiviWriteAbilitiesTest.php index 9aa40ca..4092095 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviWriteAbilitiesTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviWriteAbilitiesTest.php @@ -6,26 +6,19 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages; use Brain\Monkey\Functions; use LIT\MCP\Abilities\Rev; -use LIT\MCP\ChangeLog\ChangeLog; use LIT\MCP\Integrations\Divi\BlockTree\AttrMap; use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex; use LIT\MCP\Integrations\Divi\BlockTree\Parser; -use LIT\MCP\Integrations\Divi\Design\DesignSystemReader; -use LIT\MCP\Integrations\Divi\Pages\DiviComposition; use LIT\MCP\Integrations\Divi\Pages\DiviDeleteAbility; use LIT\MCP\Integrations\Divi\Pages\DiviDuplicateNodeAbility; use LIT\MCP\Integrations\Divi\Pages\DiviInsertAbility; use LIT\MCP\Integrations\Divi\Pages\DiviMoveAbility; -use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader; use LIT\MCP\Integrations\Divi\Pages\DiviSaver; use LIT\MCP\Integrations\Divi\Pages\DiviUpdateAbility; use LIT\MCP\Integrations\Divi\Pages\DiviWriter; -use LIT\MCP\Integrations\Divi\Schema\DiviModules; -use LIT\MCP\Tests\Unit\Integrations\Divi\Compose\ComposeFixture; -use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest; -use LIT\MCP\Tests\Unit\Integrations\Divi\Schema\ModuleCatalogTest; +use LIT\MCP\Tests\Support\DiviFixtures; +use LIT\MCP\Tests\Support\DiviWriterFixture; use LIT\MCP\Tests\Unit\TestCase; -use Mockery; use WP_Error; use WP_Post; @@ -42,34 +35,10 @@ final class DiviWriteAbilitiesTest extends TestCase protected function setUp(): void { parent::setUp(); - $this->original = DiviPageLoaderTest::fixture('synthetic-basic.html'); - $this->post = DiviPageLoaderTest::givenPost($this->original); + $this->original = DiviFixtures::markup('synthetic-basic.html'); + $this->post = DiviFixtures::givenPost($this->original); Functions\when('get_preview_post_link')->justReturn('https://kunde.test/?page_id=7&preview=true'); - - $modules = Mockery::mock(DiviModules::class); - $modules->allows('catalog')->andReturn(ModuleCatalogTest::sampleCatalog()); - $modules->allows('builderVersion')->andReturn('5.2.1'); - $design = Mockery::mock(DesignSystemReader::class); - $design->allows('read')->andReturn(DesignSystemTest::sample()); - $composition = new DiviComposition($modules, $design, ComposeFixture::sanitizer(), ComposeFixture::media()); - - $saver = Mockery::mock(DiviSaver::class); - $saver->allows('save')->andReturnUsing(function (WP_Post $post, string $content): array { - $this->saved[] = $content; - $post->post_content = $content; - - return ['revision_id' => 811, 'warnings' => []]; - }); - $log = Mockery::mock(ChangeLog::class); - $log->allows('record')->andReturn(4711); - - $this->writer = new DiviWriter( - new DiviPageLoader(DiviPageLoaderTest::schema()), - $saver, - $log, - $design, - $composition - ); + $this->writer = DiviWriterFixture::writer($this->saved); } private function treeRev(): string @@ -274,7 +243,7 @@ final class DiviWriteAbilitiesTest extends TestCase /** StructureRules apply on move: a module directly into a section, or into a specialty column. */ public function testMoveRefusedByStructureRules(): void { - $mixed = DiviPageLoaderTest::fixture('synthetic-mixed.html'); + $mixed = DiviFixtures::markup('synthetic-mixed.html'); $this->post->post_content = $mixed; $treeRev = Rev::of($mixed); @@ -342,7 +311,7 @@ final class DiviWriteAbilitiesTest extends TestCase /** An opaque/global module is removed as a whole – it never gets descended into. */ public function testDeleteRemovesAnOpaqueGlobalModuleAsAWhole(): void { - $mixed = DiviPageLoaderTest::fixture('synthetic-mixed.html'); + $mixed = DiviFixtures::markup('synthetic-mixed.html'); $this->post->post_content = $mixed; $globalRow = '