feat: Pods fields in Theme Builder bodies checked against the template's post types (E12)
Both forms Divi writes are recognized (ruling Q11): custom_meta_<key> and the dialog form post_meta_key + select_meta_key. Choice fields get a note about the stored value, relation fields name IDs (F4). Only templates the user may edit are checked or named; others are counted (Q21 pattern). Warnings only, also in the dry run of a live body. DynamicFields extracted from lit/divi-dynamic-sources. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ use LIT\MCP\Integrations\Divi\Pages\DiviInsertAbility;
|
||||
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\Pages\DynamicFields;
|
||||
use LIT\MCP\Integrations\Divi\Pages\VisibleEdits;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\Template;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\ThemeBuilderEdits;
|
||||
@@ -48,7 +49,7 @@ final class DiviWriterThemeBuilderTest extends TestCase
|
||||
}
|
||||
|
||||
/** @param list<Template> $templates */
|
||||
private function writer(array $templates, ?VisibleEdits $visible = null): DiviWriter
|
||||
private function writer(array $templates, ?VisibleEdits $visible = null, ?DynamicFields $fields = null): DiviWriter
|
||||
{
|
||||
$access = ThemeBuilderFixtures::access([], true, ThemeBuilderFixtures::store($templates));
|
||||
|
||||
@@ -58,7 +59,7 @@ final class DiviWriterThemeBuilderTest extends TestCase
|
||||
false,
|
||||
ThemeBuilderFixtures::loader($access),
|
||||
null,
|
||||
$visible === null ? new ThemeBuilderEdits($access) : new ThemeBuilderEdits($access, $visible)
|
||||
new ThemeBuilderEdits($access, $visible ?? new VisibleEdits(), $fields)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,4 +193,33 @@ final class DiviWriterThemeBuilderTest extends TestCase
|
||||
self::assertSame(1065, $entry['templates'][0]['id']);
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
private static function unknownFields(): DynamicFields
|
||||
{
|
||||
$fields = Mockery::mock(DynamicFields::class);
|
||||
$fields->allows('reason')->andReturn('kein bekanntes Feld dieses Inhaltstyps');
|
||||
$fields->allows('note')->andReturnNull();
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/** E12: an unsuitable field in a body is a warning – saved anyway; the dry run of a live body shows it too. */
|
||||
public function testFieldWarningsInTheResultAndInTheDryRun(): void
|
||||
{
|
||||
$field = ['nodes' => [['type' => 'text', 'content' => '{{field:telefon}}']]];
|
||||
$disabled = ThemeBuilderFixtures::template(1066, ['use_on' => ['singular:post_type:lit_person:all']]);
|
||||
|
||||
$result = (new DiviInsertAbility($this->writer([$disabled], null, self::unknownFields())))
|
||||
->execute($this->insert($this->body, $field));
|
||||
self::assertTrue($result['ok']);
|
||||
self::assertCount(1, $this->saved);
|
||||
self::assertStringContainsString('„telefon“', implode(' ', $result['warnings']));
|
||||
|
||||
$live = ThemeBuilderFixtures::template(1066, ['enabled' => true, 'use_on' => ['singular:post_type:lit_person:all']]);
|
||||
$dry = (new DiviInsertAbility($this->writer([$live], null, self::unknownFields())))
|
||||
->execute($this->insert($this->body, $field));
|
||||
self::assertToolError($dry, 'confirm_required');
|
||||
self::assertStringContainsString('„telefon“', implode(' ', $dry->get_error_data()['warnings']));
|
||||
self::assertCount(1, $this->saved);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DynamicFields;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\FieldDefinition;
|
||||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
|
||||
final class DynamicFieldsTest extends TestCase
|
||||
{
|
||||
private static function fields(): DynamicFields
|
||||
{
|
||||
$schema = Mockery::mock(ContentSchema::class);
|
||||
$schema->allows('fields')->with('lit_person')->andReturn([
|
||||
new FieldDefinition('role', 'Funktion', 'string', 'pods', FieldDefinition::WRITABLE),
|
||||
new FieldDefinition('level', 'Stufe', 'choice', 'pods', FieldDefinition::WRITABLE),
|
||||
new FieldDefinition('mentor', 'Mentor', 'relation', 'pods', FieldDefinition::WRITABLE),
|
||||
]);
|
||||
|
||||
return new DynamicFields($schema, static fn (string $type): ?string => 'meta');
|
||||
}
|
||||
|
||||
public function testTextLikeFieldsAreOfferedOthersOmittedWithTheirReason(): void
|
||||
{
|
||||
[$offered, $omitted] = self::fields()->offered('lit_person');
|
||||
|
||||
self::assertSame(['role', 'level'], array_column($offered, 'key'));
|
||||
self::assertSame('{{field:role}}', $offered[0]['placeholder']);
|
||||
self::assertSame(['mentor'], array_column($omitted, 'key'));
|
||||
}
|
||||
|
||||
public function testReasonForOneKey(): void
|
||||
{
|
||||
self::assertNull(self::fields()->reason('lit_person', 'role'));
|
||||
self::assertStringContainsString('nur IDs', (string) self::fields()->reason('lit_person', 'mentor'));
|
||||
self::assertSame('kein bekanntes Feld dieses Inhaltstyps', self::fields()->reason('lit_person', 'telefon'));
|
||||
}
|
||||
|
||||
/** Answer F4 / stage3b render: a choice field shows its stored value ("senior"), not the label. */
|
||||
public function testChoiceFieldsGetANoteAboutTheStoredValue(): void
|
||||
{
|
||||
self::assertStringContainsString('gespeicherten Wert', (string) self::fields()->note('lit_person', 'level'));
|
||||
self::assertNull(self::fields()->note('lit_person', 'role'));
|
||||
self::assertNull(self::fields()->note('lit_person', 'mentor'));
|
||||
self::assertNull(self::fields()->note('lit_person', 'telefon'));
|
||||
}
|
||||
|
||||
private static function content(string $name, array $settings): string
|
||||
{
|
||||
return VariableRef::serialize('content', $name, $settings);
|
||||
}
|
||||
|
||||
/** Ruling Q11 / stage3b finding 12: both forms Divi resolves (DynamicContentOptionPostMetaKey.php:222-262). */
|
||||
public function testMetaKeyOfReadsBothFormsDiviWrites(): void
|
||||
{
|
||||
self::assertSame('role', DynamicFields::metaKeyOf(self::content('custom_meta_role', ['before' => ''])));
|
||||
self::assertSame('role', DynamicFields::metaKeyOf(self::content('post_meta_key', [
|
||||
'before' => '',
|
||||
'after' => '',
|
||||
'select_meta_key' => 'custom_meta_role',
|
||||
'meta_key' => '',
|
||||
'date_format' => 'default',
|
||||
'custom_date_format' => '',
|
||||
'enable_html' => 'off',
|
||||
])));
|
||||
self::assertSame('telefon', DynamicFields::metaKeyOf(self::content('post_meta_key', ['meta_key' => 'telefon'])));
|
||||
self::assertSame('telefon', DynamicFields::metaKeyOf(self::content('post_meta_key', [
|
||||
'select_meta_key' => 'custom_meta_manual_custom_field_value',
|
||||
'meta_key' => 'telefon',
|
||||
])));
|
||||
self::assertSame('telefon', DynamicFields::metaKeyOf(self::content(
|
||||
'custom_meta_manual_custom_field_value',
|
||||
['meta_key' => 'telefon']
|
||||
)));
|
||||
}
|
||||
|
||||
public function testMetaKeyOfIgnoresEverythingElse(): void
|
||||
{
|
||||
self::assertNull(DynamicFields::metaKeyOf(self::content('post_title', ['before' => ''])));
|
||||
self::assertNull(DynamicFields::metaKeyOf(self::content('post_meta_key', ['meta_key' => ''])));
|
||||
self::assertNull(DynamicFields::metaKeyOf(self::content('post_meta_key', ['select_meta_key' => 'role'])));
|
||||
self::assertNull(DynamicFields::metaKeyOf(self::content('custom_meta_', [])));
|
||||
self::assertNull(DynamicFields::metaKeyOf(
|
||||
'$variable({"type":"color","value":{"name":"custom_meta_role","settings":{}}})$'
|
||||
));
|
||||
self::assertNull(DynamicFields::metaKeyOf('custom_meta_role'));
|
||||
}
|
||||
|
||||
/** The real builder output (stage3b body 2391) names role, level and mentor. */
|
||||
public function testMetaKeysOfTheRealBuilderBody(): void
|
||||
{
|
||||
$content = DiviFixtures::markup('stage3b/tb-body--person-body.html');
|
||||
preg_match_all('/"value":"(\$variable\(.*?\)\$)"/', $content, $matches);
|
||||
$keys = [];
|
||||
foreach ($matches[1] as $value) {
|
||||
$key = DynamicFields::metaKeyOf((string) json_decode('"' . $value . '"'));
|
||||
if ($key !== null) {
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
self::assertSame(['role', 'level', 'mentor'], $keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Abilities\Rev;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviUpdateAbility;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\ThemeBuilderEdits;
|
||||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||||
use LIT\MCP\Tests\Support\DiviWriterFixture;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use LIT\MCP\Tests\Support\ThemeBuilderFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
|
||||
/** The body layout the real builder wrote with Pods fields (stage3b) stays editable and byte-identical. */
|
||||
final class Stage3bBodyEditTest extends TestCase
|
||||
{
|
||||
public function testChangingALabelKeepsEveryOtherByteIncludingThePodsReferences(): void
|
||||
{
|
||||
$content = DiviFixtures::markup('stage3b/tb-body--person-body.html');
|
||||
$body = ThemeBuilderFixtures::layout(ThemeBuilderFixtures::BODY, ['post_content' => $content]);
|
||||
LibraryFixtures::given([$body]);
|
||||
Functions\when('admin_url')->alias(static fn (string $path): string => 'https://kunde.test/wp-admin/' . $path);
|
||||
$saved = [];
|
||||
$failures = [];
|
||||
$access = ThemeBuilderFixtures::access();
|
||||
$writer = DiviWriterFixture::writer(
|
||||
$saved,
|
||||
$failures,
|
||||
false,
|
||||
ThemeBuilderFixtures::loader($access),
|
||||
null,
|
||||
new ThemeBuilderEdits($access)
|
||||
);
|
||||
|
||||
$result = (new DiviUpdateAbility($writer))->execute([
|
||||
'post_id' => 1062,
|
||||
'tree_rev' => Rev::of($content),
|
||||
'changes' => [['id' => 's0.r0.c0.m0', 'label' => 'Person Name']],
|
||||
]);
|
||||
|
||||
self::assertTrue($result['ok']);
|
||||
self::assertSame(str_replace('"value":"Person Titel"', '"value":"Person Name"', $content), $saved[0]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user