test: Ende-zu-Ende-Test der Presets gegen Divi; Guide und Entscheidungsnotiz zu 4a

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-16 17:56:07 +02:00
co-authored by Claude Sonnet 5.5
parent 327f8d2d35
commit 2ed6093f97
3 changed files with 107 additions and 0 deletions
+24
View File
@@ -22,3 +22,27 @@ Regeln:
der Nutzer es ausdrücklich möchte.
3. Gibt es für einen Zweck noch keine Farbe, Variable oder kein Preset, dem Nutzer vorschlagen, es im
Divi-Builder anzulegen, statt feste Werte zu verteilen.
## Presets anlegen, ändern, löschen
Über `mcp-adapter-execute-ability`:
- `lit/divi-preset-get` – ein Preset mit Einstellungen (`attrs` als Pfad → Wert wie bei `lit-divi-node-get`),
verschachtelten Gruppen-Presets und den Seiten, die es verwenden (`usage`).
- `lit/divi-presets-set` – `items` mit `module` (z. B. `"button"`) oder `group` (z. B. `"font-body"`):
- neu: `name` + Einstellungen; ändern: `preset` (Name oder ID), optional `rename`;
- Einstellungen in `attrs` (Vollform, Platzhalter `{{color:…}}`, `{{var:…}}`, `{{font:…}}`) oder aus einem
gestalteten Modul: `from_node` = `{"post_id": 12, "id": "s0.r0.c1.m2"}` (Pfad-ID aus `lit-divi-outline`);
- Presets tragen nur Gestaltung – Texte, Links und Bilder gehören ins Modul (sonst `validation_failed`);
- `group_presets` (nur Modul-Presets), z. B. `{"font": "Button-Schrift"}`; `make_default: true` macht das
Preset zum Standard seines Moduls bzw. seiner Gruppe;
- neue Gruppen-Presets brauchen das Modul, aus dem die Gruppe stammt: `source_module` oder `from_node`.
- `lit/divi-presets-delete` – nur Presets, die nirgends verwendet werden; sonst `in_use` mit den Fundstellen.
- `lit/divi-design-snapshots` / `lit/divi-design-restore` – vor jeder Änderung legt LIT MCP einen Snapshot an
(je Bereich die letzten 20); wiederherstellen mit `snapshot_id`.
Ablauf jeder Änderung: Aufruf ohne `confirm` = Probelauf (`confirm_required` mit `diff`, betroffenen Seiten und
`design_rev`), dem Nutzer die Änderung zeigen, dann derselbe Aufruf mit `confirm: true` und `design_rev`. Hat sich
der Bestand inzwischen geändert (z. B. im Divi-Builder), kommt `rev_mismatch` – neu prüfen.
Rechte: Presets ändern verlangt das Recht, das Design anzupassen, und in Divis Rollen-Editor den Visual Builder.
@@ -30,3 +30,9 @@ Stand 2026-09-28, Brainstorming läuft (Weg: architektonisch → Spec → Plan).
## Danach
Ansätze vorschlagen → Entwurf abschnittsweise freigeben → Spec schreiben → Plan (writing-plans) → Umsetzung subagent-driven mit Reviews, Security-Audit am Ende.
## Umsetzung 4a (Plan 2026-10-16-etappe-4a-presets.md)
- Pipeline `DesignChange` (alle Bereiche), Snapshots `SnapshotStore`, Bereich `presets`.
- Tools: lit/divi-preset-get, lit/divi-presets-set, lit/divi-presets-delete, lit/divi-design-snapshots, lit/divi-design-restore (alle indirekt).
- Schreiben über die Schrittfolge von `GlobalPresetController::sync` (Mirror `preset-sync`).
- Offen für 4b: Fundstellen von Standard-Gruppen-Presets (Gruppenliste je Modul), lit/divi-design-detach.
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# End-to-end checks of Etappe 4a (presets: read, create, change, default, delete, snapshots, restore) through the
# OAuth-protected endpoint against the real Divi. Runs INSIDE the DDEV web container:
# cd /home/janniclas/Projekte/WordPress && timeout 900 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-presets-smoke.sh
# Creates presets named "LIT Smoke <run> …" and one draft page; the EXIT trap restores the preset inventory from the
# snapshot taken before the first change and deletes the page (with revisions) by exact ID.
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)
RUN="$(date +%s)-$$"
PREFIX="LIT Smoke $RUN"
ORIGINAL_PRESETS=$(wp eval 'echo wp_json_encode(ET\Builder\Packages\GlobalData\GlobalPreset::get_data());' 2>/dev/null)
PAGE_ID=""
cleanup() {
wp eval '$d = json_decode(getenv("LIT_PRESETS"), true); et_update_option(ET\Builder\Packages\GlobalData\GlobalPreset::option_name(), $d, false, "", "", true);' \
--user="$ADMIN_LOGIN" >/dev/null 2>&1 || echo "WARN: preset inventory not restored"
if [ -n "$PAGE_ID" ]; then wp post delete "$PAGE_ID" --force >/dev/null 2>&1 || true; fi
cleanup_app_passwords_for "$ADMIN_LOGIN"
}
export LIT_PRESETS="$ORIGINAL_PRESETS"
trap 'rc=$?; cleanup; guard_exit "$rc" || exit $?' EXIT
connect "$ADMIN_LOGIN"
# 1. Create a button preset: dry run, then confirm.
NEW=$(jq -cn --arg n "$PREFIX CTA" '{items: [{module: "button", name: $n,
attrs: {"button.decoration.background": {desktop: {color: "#123456"}}}}]}')
DRY=$(ability lit/divi-presets-set "$NEW")
[ "$(error_code "$DRY")" = "confirm_required" ] || fail "create without confirm: $DRY"
REV=$(echo "$DRY" | jq -r '.__error | fromjson | .design_rev')
DONE=$(ability lit/divi-presets-set "$(echo "$NEW" | jq -c --arg r "$REV" '. + {confirm: true, design_rev: $r}')")
[ "$(echo "$DONE" | jq -r '.ok')" = "true" ] || fail "create with confirm: $DONE"
SNAPSHOT=$(echo "$DONE" | jq -r '.snapshot_id')
pass "preset created (snapshot $SNAPSHOT)"
# 2. Read it back through Divi and the tool.
GET=$(ability lit/divi-preset-get "$(jq -cn --arg p "$PREFIX CTA" '{module: "button", preset: $p}')")
ID=$(echo "$GET" | jq -r '.id')
[ "$(echo "$GET" | jq -r '.attrs["button.decoration.background"].desktop.value.color')" = "#123456" ] \
|| fail "preset-get attrs: $GET"
wp eval "\$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data(); exit(isset(\$d['module']['divi/button']['items']['$ID']['styleAttrs']) ? 0 : 1);" \
|| fail "Divi does not store styleAttrs for $ID"
pass "preset readable, styleAttrs stored"
# 3. A page with the preset renders its class.
PAGE_ID=$(wp post create --post_type=page --post_status=draft --post_title="$PREFIX Seite" \
--post_content="<!-- wp:divi/placeholder --><!-- wp:divi/section --><!-- wp:divi/row --><!-- wp:divi/column {\"module\":{\"decoration\":{\"sizing\":{\"desktop\":{\"value\":{\"flexType\":\"24_24\"}}}}}} --><!-- wp:divi/button {\"modulePreset\":[\"$ID\"],\"button\":{\"innerContent\":{\"desktop\":{\"value\":{\"text\":\"Los\"}}}}} /--><!-- /wp:divi/column --><!-- /wp:divi/row --><!-- /wp:divi/section --><!-- /wp:divi/placeholder -->" \
--porcelain)
HTML=$(wp eval "echo apply_filters('the_content', get_post($PAGE_ID)->post_content);")
echo "$HTML" | grep -q "preset--module--divi-button--$ID" || fail "rendered page lacks the preset class"
pass "page renders preset class"
# 4. Deleting a used preset is refused.
DEL=$(jq -cn --arg p "$ID" '{items: [{module: "button", preset: $p}]}')
[ "$(error_code "$(ability lit/divi-presets-delete "$DEL")")" = "in_use" ] || fail "delete of a used preset"
pass "used preset not deletable"
# 5. Stale design_rev.
STALE=$(ability lit/divi-presets-set "$(echo "$NEW" | jq -c '.items[0].name += " 2" | . + {confirm: true, design_rev: "veraltet"}')")
[ "$(error_code "$STALE")" = "rev_mismatch" ] || fail "stale design_rev: $STALE"
pass "stale design_rev refused"
# 6. Restore the snapshot taken before step 1: the preset is gone again.
DRY=$(ability lit/divi-design-restore "$(jq -cn --arg s "$SNAPSHOT" '{snapshot_id: $s}')")
[ "$(error_code "$DRY")" = "confirm_required" ] || fail "restore dry run: $DRY"
REV=$(echo "$DRY" | jq -r '.__error | fromjson | .design_rev')
RESTORED=$(ability lit/divi-design-restore "$(jq -cn --arg s "$SNAPSHOT" --arg r "$REV" '{snapshot_id: $s, confirm: true, design_rev: $r}')")
[ "$(echo "$RESTORED" | jq -r '.ok')" = "true" ] || fail "restore: $RESTORED"
wp eval "\$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data(); exit(isset(\$d['module']['divi/button']['items']['$ID']) ? 1 : 0);" \
|| fail "restored inventory still has $ID"
pass "snapshot restored"
finish