feat: Divi hints point to the read tools; guides divi-structure/attrs/design-system; test: Divi read end-to-end
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:
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-to-end checks of the Etappe-2a Divi read tools through the OAuth-protected endpoint.
|
||||
# Runs INSIDE the DDEV web container (Divi 5 must be the active theme):
|
||||
# cd /home/janniclas/Projekte/WordPress && timeout 600 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-read-smoke.sh
|
||||
# Creates draft pages from tests/Fixtures/divi and deletes them again.
|
||||
set -euo pipefail
|
||||
cd /var/www/html
|
||||
# shellcheck source=lib.sh
|
||||
source wp-content/plugins/WP-LIT-MCP/tests/Integration/lib.sh
|
||||
|
||||
ADMIN_LOGIN=$(wp user list --role=administrator --field=user_login | head -1)
|
||||
FIXTURES=$PLUGIN/tests/Fixtures/divi
|
||||
CREATED_POSTS=()
|
||||
|
||||
cleanup() {
|
||||
for id in "${CREATED_POSTS[@]}"; do
|
||||
wp post delete "$id" --force >/dev/null 2>&1 || true
|
||||
done
|
||||
cleanup_app_passwords_for "$ADMIN_LOGIN"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Creates a draft page from a fixture file as the admin (no kses re-serialization) and prints its ID.
|
||||
create_page() { # $1 = fixture file, $2 = title
|
||||
LIT_FILE="$FIXTURES/$1" LIT_TITLE="$2" wp --user="$ADMIN_LOGIN" eval '
|
||||
$id = wp_insert_post(["post_type" => "page", "post_status" => "draft", "post_title" => getenv("LIT_TITLE"),
|
||||
"post_content" => wp_slash((string) file_get_contents(getenv("LIT_FILE")))]);
|
||||
update_post_meta($id, "_et_pb_use_builder", "on");
|
||||
update_post_meta($id, "_et_pb_use_divi_5", "on");
|
||||
echo $id;'
|
||||
}
|
||||
|
||||
content_hash() { # $1 = post ID -> tree_rev of the stored content
|
||||
wp eval "echo substr(hash('sha256', (string) get_post($1)->post_content), 0, 12);"
|
||||
}
|
||||
|
||||
[ "$(wp eval 'echo get_template();')" = "Divi" ] || fail "Divi must be the active theme"
|
||||
wp eval-file "$PLUGIN/tests/Integration/divi-shortform-check.php" >/dev/null || fail "short form does not match the Divi catalog"
|
||||
pass "short form matches the live Divi catalog"
|
||||
|
||||
PAGE_ID=$(create_page converted-landing.html "LIT Divi Lesen")
|
||||
CREATED_POSTS+=("$PAGE_ID")
|
||||
D4_ID=$(create_page divi4-shortcodes.html "LIT Divi 4")
|
||||
CREATED_POSTS+=("$D4_ID")
|
||||
BEFORE=$(content_hash "$PAGE_ID")
|
||||
connect "$ADMIN_LOGIN"
|
||||
|
||||
# 1. Direct read tools with readOnlyHint
|
||||
LIST=$(rpc tools/list '{}')
|
||||
for t in lit-divi-outline lit-divi-node-get lit-divi-catalog lit-divi-design-system lit-page-render-summary; do
|
||||
echo "$LIST" | jq -e --arg t "$t" '.result.tools[] | select(.name == $t) | .annotations.readOnlyHint == true' >/dev/null \
|
||||
|| fail "tools/list lacks $t with readOnlyHint"
|
||||
done
|
||||
pass "tools/list: Divi read tools are direct and read-only"
|
||||
|
||||
# 2. Outline with tree_rev = hash of post_content (ruling P16) and path IDs
|
||||
OUTLINE=$(tool lit-divi-outline "{\"post_id\":$PAGE_ID}")
|
||||
[ "$(echo "$OUTLINE" | jq -r '.tree_rev')" = "$BEFORE" ] || fail "tree_rev is not the hash of post_content: $OUTLINE"
|
||||
echo "$OUTLINE" | jq -e '.rev != .tree_rev' >/dev/null || fail "rev and tree_rev must differ: $OUTLINE"
|
||||
echo "$OUTLINE" | jq -e '.nodes[] | select(.id == "s1.r1.c0.m2.m0" and .type == "accordion-item")' >/dev/null \
|
||||
|| fail "outline lacks the accordion item: $OUTLINE"
|
||||
echo "$OUTLINE" | jq -r '.outline' | head -1 | grep -q '^s0 section "Hero"' || fail "outline text: $OUTLINE"
|
||||
pass "lit-divi-outline: path IDs, tree_rev, rev"
|
||||
|
||||
# 3. Node in full form plus short-form values
|
||||
NODE=$(tool lit-divi-node-get "{\"post_id\":$PAGE_ID,\"id\":\"s0.r0.c0.m0\"}")
|
||||
echo "$NODE" | jq -e '.type == "divi/heading" and .shortform.text == "Handwerk mit Herz"' >/dev/null || fail "node-get: $NODE"
|
||||
echo "$NODE" | jq -e '.attrs["title.innerContent"].desktop.value == "Handwerk mit Herz"' >/dev/null || fail "node-get attrs: $NODE"
|
||||
MISSING=$(tool lit-divi-node-get "{\"post_id\":$PAGE_ID,\"id\":\"s9\"}")
|
||||
[ "$(error_code "$MISSING")" = "not_found" ] || fail "unknown node: $MISSING"
|
||||
pass "lit-divi-node-get: full form, short form, unknown node"
|
||||
|
||||
# 4. Catalog and design system live from Divi
|
||||
CATALOG=$(tool lit-divi-catalog '{}')
|
||||
echo "$CATALOG" | jq -e '(.modules | length) >= 90' >/dev/null || fail "catalog too small: $CATALOG"
|
||||
BUTTON=$(tool lit-divi-catalog '{"module":"button"}')
|
||||
echo "$BUTTON" | jq -e '.module.paths | index("button.innerContent") != null' >/dev/null || fail "catalog button: $BUTTON"
|
||||
DESIGN=$(tool lit-divi-design-system '{}')
|
||||
echo "$DESIGN" | jq -e '.colors[] | select(.id == "gcid-primary-color" and .placeholder == "{{color:primary}}")' >/dev/null \
|
||||
|| fail "design system: $DESIGN"
|
||||
pass "lit-divi-catalog and lit-divi-design-system read live data"
|
||||
|
||||
# 5. Rendered summary
|
||||
SUMMARY=$(tool lit-page-render-summary "{\"post_id\":$PAGE_ID}")
|
||||
echo "$SUMMARY" | jq -e '.headings[0].text == "Handwerk mit Herz"' >/dev/null || fail "render summary: $SUMMARY"
|
||||
echo "$SUMMARY" | jq -e '[.texts[] | select(contains("Seit 1987"))] | length == 1' >/dev/null || fail "render texts: $SUMMARY"
|
||||
pass "lit-page-render-summary renders Divi modules"
|
||||
|
||||
# 6. Hints and refusals
|
||||
GET=$(tool lit-content-get "{\"post_id\":$PAGE_ID}")
|
||||
echo "$GET" | jq -e '.content == null and (.hints[0] | contains("lit-divi-outline"))' >/dev/null || fail "content-get hint: $GET"
|
||||
D4=$(tool lit-divi-outline "{\"post_id\":$D4_ID}")
|
||||
[ "$(error_code "$D4")" = "unsupported" ] || fail "Divi 4 page must be unsupported: $D4"
|
||||
pass "content-get points to the Divi tools; Divi 4 content is refused"
|
||||
|
||||
# 7. Reading changed nothing
|
||||
[ "$(content_hash "$PAGE_ID")" = "$BEFORE" ] || fail "reading changed post_content"
|
||||
pass "post_content unchanged after reading"
|
||||
|
||||
echo "ALL CHECKS PASSED"
|
||||
@@ -45,6 +45,22 @@ final class InstructionsBuilderTest extends TestCase
|
||||
self::assertLessThan(2000, strlen($text), 'Instructions must stay short – they are loaded in every conversation.');
|
||||
}
|
||||
|
||||
public function testAddsTheDiviRuleOnlyWhenDiviIsActive(): void
|
||||
{
|
||||
$divi = Mockery::mock(Integration::class);
|
||||
$divi->allows('id')->andReturn('divi');
|
||||
$divi->allows('isAvailable')->andReturn(true);
|
||||
$divi->allows('abilities')->andReturn([$this->ability('lit/divi-outline', true)]);
|
||||
$core = $this->coreWith($this->ability('lit/guide', true));
|
||||
|
||||
$with = (new InstructionsBuilder(new IntegrationRegistry($core, $divi)))->build();
|
||||
$without = (new InstructionsBuilder(new IntegrationRegistry($core)))->build();
|
||||
|
||||
self::assertStringContainsString('lit-divi-outline und lit-divi-design-system lesen', $with);
|
||||
self::assertStringNotContainsString('lit-divi-outline und', $without);
|
||||
self::assertLessThan(2000, strlen($with));
|
||||
}
|
||||
|
||||
public function testListsPostTypesWhenAProviderIsGiven(): void
|
||||
{
|
||||
$core = $this->coreWith($this->ability('lit/guide', true));
|
||||
|
||||
@@ -10,6 +10,7 @@ use LIT\MCP\Abilities\Rev;
|
||||
use LIT\MCP\Abilities\WriteFailed;
|
||||
use LIT\MCP\ChangeLog\ChangeLog;
|
||||
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\Unit\TestCase;
|
||||
@@ -116,9 +117,7 @@ final class ContentUpdateAbilityTest extends TestCase
|
||||
->execute(['post_id' => 5, 'rev' => Rev::ofPost($this->post), 'content' => '<p>Neu</p>']);
|
||||
|
||||
self::assertSame('lit_unsupported', $result->get_error_code());
|
||||
self::assertStringNotContainsString('lit-divi', $result->get_error_message());
|
||||
self::assertStringContainsString('Divi-Werkzeuge folgen in einer späteren Version; bis dahin im Divi-Builder '
|
||||
. 'bearbeiten.', $result->get_error_data()['hint']);
|
||||
self::assertStringContainsString(PostPresenter::DIVI_INACTIVE_NOTE, $result->get_error_data()['hint']);
|
||||
}
|
||||
|
||||
public function testNothingToChangeIsAValidationError(): void
|
||||
|
||||
@@ -87,13 +87,20 @@ final class PostPresenterTest extends TestCase
|
||||
self::assertTrue($full['divi']);
|
||||
self::assertNull($full['content']);
|
||||
self::assertNull($full['featured_image']);
|
||||
self::assertStringNotContainsString('lit-divi', $full['hints'][0]);
|
||||
self::assertStringContainsString('Divi-Werkzeuge folgen in einer späteren Version', $full['hints'][0]);
|
||||
self::assertSame([PostPresenter::diviHint(false)], $full['hints']);
|
||||
self::assertInstanceOf(stdClass::class, $full['terms']);
|
||||
self::assertInstanceOf(stdClass::class, $full['fields']);
|
||||
self::assertSame(Rev::ofPost($post), $full['rev']);
|
||||
}
|
||||
|
||||
public function testDiviHintNamesTheDiviToolsOnlyWhenDiviIsActive(): void
|
||||
{
|
||||
self::assertStringContainsString('lit-divi-outline', PostPresenter::diviHint(true));
|
||||
self::assertStringContainsString('lit-content-update', PostPresenter::diviHint(true));
|
||||
self::assertStringNotContainsString('lit-divi', PostPresenter::diviHint(false));
|
||||
self::assertStringContainsString(PostPresenter::DIVI_INACTIVE_NOTE, PostPresenter::diviHint());
|
||||
}
|
||||
|
||||
public function testPasswordAndProtectedReadonlyMetaFieldsAreOmitted(): void
|
||||
{
|
||||
$this->stubLinks();
|
||||
|
||||
@@ -66,7 +66,7 @@ final class RevisionsListAbilityTest extends TestCase
|
||||
|
||||
self::assertTrue($result['items'][0]['divi']);
|
||||
self::assertFalse($result['items'][1]['divi']);
|
||||
self::assertSame([PostPresenter::DIVI_HINT], $result['hints']);
|
||||
self::assertSame([PostPresenter::diviHint()], $result['hints']);
|
||||
}
|
||||
|
||||
public function testRevisionsOfATrashedPostCanStillBeListed(): void
|
||||
|
||||
Reference in New Issue
Block a user