From 10bed73b244e67b0c76992b5acfe6d70974431c9 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sun, 18 Oct 2026 17:25:16 +0200 Subject: [PATCH] =?UTF-8?q?test:=20Ende-zu-Ende-Test=20der=20Zuweisung=20(?= =?UTF-8?q?Stapel,=20Gruppen,=20=C3=9Cberdeckung,=20Massenzuweisung,=20det?= =?UTF-8?q?ach);=20Entscheidungsnotiz=20zu=204b?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- ...6-09-28-etappe-4-entscheidungen-entwurf.md | 21 +- .../Integration/divi-presets-assign-smoke.sh | 252 ++++++++++++++++++ 2 files changed, 272 insertions(+), 1 deletion(-) create mode 100755 tests/Integration/divi-presets-assign-smoke.sh diff --git a/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md b/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md index f515bc9..8cd213f 100644 --- a/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md +++ b/docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md @@ -35,7 +35,26 @@ Ansätze vorschlagen → Entwurf abschnittsweise freigeben → Spec schreiben - 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. +- Offen für 4b (erledigt in 4b, R11 und Task 10): Fundstellen von Standard-Gruppen-Presets (Gruppenliste je Modul), lit/divi-design-detach. + +## Etappe 4b (Plan 2026-10-18-etappe-4b-zuweisen.md) +- Tools: lit-divi-insert/-update/lit-content-create (preset-Stapel, group_presets, clear_overrides), lit/divi-presets-assign, lit/divi-design-detach (beide indirekt); Outline und node-get zeigen Gruppen-Presets. +- R1 Fundstellen: keine Klasse DesignUsages; GlobalUsages::reportFor()/rowsFor() plus ModuleUsageQuery (alle Module eines Typs). +- R2 Stapel: preset = String | Liste (1–10) | null; doppelte IDs und leere Liste sind Fehler; letzter im Stapel gewinnt bei gleicher Priorität. +- R3 Gruppen-Schlüssel: Kurzname, voller Name oder Attributpfad; mehrdeutiger Name → Fehler mit den Pfaden; gleichnamige Gruppen in einem Modul sind auch in lit/divi-presets-set und im Composer (group_presets) ein Fehler, dort hilft der Attributpfad als Schlüssel; GroupKeys ersetzt die private Suche im PresetComposer. +- R4 Überdeckungen (G9): Vergleich je Blatt (Pfad, Breakpoint, Zustand), nur ausdrücklich zugewiesene Presets, Warnung nur bei abweichendem Wert; clear_overrides entfernt alle lokalen Blätter auf Preset-Blättern. +- R5 Warnungstext je Preset eine Zeile, bis zu 5 Pfade. +- R6 Outline: group_presets je Zeile und in node-get; Fingerprint unverändert. +- R7 clear_overrides beim Einfügen wirkt nach set/attrs desselben Knotens; nicht-boolesch → Fehler. +- R8 Seiten-Umschreiber: globale Instanzen nie umgeschrieben (skipped; Bibliotheks-ID nur bei sichtbaren Bibliotheks-IDs, P13), Bibliothekseintrag ist eigene Fundstelle; nicht ladbare Seiten skipped; remaining zählt nur Seiten mit Änderungen. +- P8a mit post_ids: nur die genannten Seiten können das Schreiben blockieren, HARD_CAP gilt nicht, eine genannte Seite ohne Bearbeitungsrecht steht in skipped. Ohne post_ids verweigert jede blockierte Live-Fundstelle oder zu viele Fundstellen den ganzen Aufruf, nichts wird geschrieben. +- R9 Kein unfiltered_html-Zwang: der Umschreiber ändert nur Preset-Verweise und entfernt lokale Werte, Speichern über DiviSaver. +- R10 detach nur Presets: Eingabe preset = Objekt {module|group, preset}; variable → unsupported (4c); ohne post_ids zuerst verschachtelte Verweise über DesignChange (Snapshot, design_rev), danach Seiten (≤ 50); scheitert die erste Seite danach, nennt der Fehler Snapshot und diff; Standard-Presets nur ausdrückliche Verweise. +- lit/divi-presets-assign: preset: null und null in group_presets werden abgewiesen (Hinweis auf lit/divi-design-detach); ein leerer Probelauf ist ein normales „nichts zu tun“. +- R11 Standard-Gruppen-Presets: PresetUsageMatcher erkennt implizite Nutzung (Modul hat die Gruppe, kein ausdrücklicher Eintrag); Kandidaten fail closed über wp:divi/; der 4a-Hinweis „nicht aufgelistet“ entfällt. +- Grenze je Aufruf A6: 50 Seiten, remaining; Teilerfolg partial_write mit den gespeicherten Seiten. +- Offen für 4c: detach für Variablen (A7: Referenz durch den aktuellen Wert ersetzen, bei Farben entfällt detach), Wiederverwendung des PageRewriter/TreeRewrite dafür (NodeRewrite-Strategie `VariableDetachRewrite`). +- Offen für 4c/4e: Smoke divi-presets-assign-smoke.sh läuft nur mit deaktivierten Plugins postlos und asynclistry. ## Offene Kleinpunkte aus 4a (Reviews, nicht merge-relevant) - Task 1: minor (deferred): Nachtrag verweist nicht auf §7 Punkt 4 („strenger als Divi“ gilt für Presets nicht mehr) diff --git a/tests/Integration/divi-presets-assign-smoke.sh b/tests/Integration/divi-presets-assign-smoke.sh new file mode 100755 index 0000000..b2c69c3 --- /dev/null +++ b/tests/Integration/divi-presets-assign-smoke.sh @@ -0,0 +1,252 @@ +#!/usr/bin/env bash +# End-to-end checks of Etappe 4b (assign presets to modules, overrides, mass assignment, detach, delete) 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-assign-smoke.sh +# Runs only with the plugins postlos and asynclistry deactivated (Monolog 2 / deprecation output of the WP-CLI calls). +# Creates presets named "LIT Smoke …" and two draft pages with one button each; the EXIT trap restores the +# preset inventory taken before the first change and deletes the pages (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) +PAGE1="" +PAGE2="" +NODE="s0.r0.c0.m0" +CONTENT='' + +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" + for id in $PAGE1 $PAGE2; do + wp post delete "$id" --force >/dev/null 2>&1 || true + done + # Change-log rows about the test pages. + LIT_IDS="$PAGE1 $PAGE2" wp eval ' +global $wpdb; +$table = $wpdb->prefix . "lit_mcp_log"; +foreach (array_filter(explode(" ", (string) getenv("LIT_IDS"))) as $id) { + $wpdb->delete($table, ["object_type" => "page", "object_id" => $id]); +}' >/dev/null 2>&1 || true + cleanup_app_passwords_for "$ADMIN_LOGIN" +} +export LIT_PRESETS="$ORIGINAL_PRESETS" +trap 'rc=$?; cleanup; guard_exit "$rc" || exit $?' EXIT + +connect "$ADMIN_LOGIN" + +create_page() { # $1 = title -> prints the ID of a draft Divi 5 page with one button without preset + LIT_TITLE="$1" LIT_CONTENT="$CONTENT" 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) getenv("LIT_CONTENT"))]); +update_post_meta($id, "_et_pb_use_builder", "on"); +update_post_meta($id, "_et_pb_use_divi_5", "on"); +echo $id;' 2>/dev/null +} + +button_attrs() { # $1 = post ID -> attributes (JSON) of the first divi/button block as stored + LIT_ID="$1" wp eval ' +$find = static function (array $blocks) use (&$find) { + foreach ($blocks as $b) { + if ($b["blockName"] === "divi/button") { return $b["attrs"]; } + $r = $find($b["innerBlocks"]); + if ($r !== null) { return $r; } + } + return null; +}; +echo wp_json_encode($find(parse_blocks(get_post((int) getenv("LIT_ID"))->post_content)));' 2>/dev/null +} + +revisions_of() { # $1 = post ID -> number of revisions + LIT_ID="$1" wp eval 'echo count(wp_get_post_revisions((int) getenv("LIT_ID")));' 2>/dev/null +} + +preset_stored() { # $1 = module|group, $2 = divi/, $3 = preset ID -> exit 0 when Divi stores it + LIT_KIND="$1" LIT_SCOPE="$2" LIT_PID="$3" wp eval ' +$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data(); +exit(isset($d[getenv("LIT_KIND")][getenv("LIT_SCOPE")]["items"][getenv("LIT_PID")]) ? 0 : 1);' 2>/dev/null +} + +preset_item() { # $1 = module preset ID -> the stored button preset item (JSON) + LIT_PID="$1" wp eval ' +$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data(); +echo wp_json_encode($d["module"]["divi/button"]["items"][getenv("LIT_PID")] ?? null);' 2>/dev/null +} + +nested_groups() { # $1 = module preset ID -> groupPresets (JSON) of that button preset + LIT_PID="$1" wp eval ' +$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data(); +echo wp_json_encode($d["module"]["divi/button"]["items"][getenv("LIT_PID")]["groupPresets"] ?? new stdClass());' 2>/dev/null +} + +timed() { # $1 = label; runs the rest of the arguments and prints the duration to stderr + local label=$1 start end + shift + start=$(date +%s.%N) + "$@" + end=$(date +%s.%N) + echo "time - $label: $(awk -v s="$start" -v e="$end" 'BEGIN { printf "%.2f", e - s }') s" >&2 +} + +details() { # $1 = tool()/ability() result -> details object of an error, or the result itself + echo "$1" | jq -c 'if has("__error") then ((.__error | fromjson?) // {}) else . end' +} + +# Runs lit/divi-presets-set with dry run and confirm; sets RESULT. $1 = input JSON without confirm. +presets_set() { + local dry rev + dry=$(ability lit/divi-presets-set "$1") + [ "$(error_code "$dry")" = "confirm_required" ] || fail "presets-set dry run: $dry" + rev=$(echo "$dry" | jq -r '.__error | fromjson | .design_rev') + RESULT=$(ability lit/divi-presets-set "$(echo "$1" | jq -c --arg r "$rev" '. + {confirm: true, design_rev: $r}')") + [ "$(echo "$RESULT" | jq -r '.ok')" = "true" ] || fail "presets-set confirm: $RESULT" +} + +preset_id() { # $1 = module|group, $2 = short name ("button", "font"), $3 = preset name -> prints the ID + local id + id=$(ability lit/divi-preset-get "$(jq -cn --arg k "$1" --arg s "$2" --arg p "$3" '{($k): $s, preset: $p}')" | jq -r '.id // empty') + [ -n "$id" ] || fail "preset $3 not found" + echo "$id" +} + +tree_rev() { # $1 = post ID + tool lit-divi-outline "{\"post_id\":$1}" | jq -r '.tree_rev' +} + +# 1. Two draft pages with one button without preset each. +PAGE1=$(create_page "$PREFIX Seite 1") +PAGE2=$(create_page "$PREFIX Seite 2") +[ -n "$PAGE1" ] && [ -n "$PAGE2" ] || fail "pages not created" +for id in "$PAGE1" "$PAGE2"; do + button_attrs "$id" | jq -e '(.modulePreset // null) == null and (.groupPreset // null) == null' >/dev/null \ + || fail "page $id starts with a preset" +done +pass "two draft pages with a button without preset ($PAGE1, $PAGE2)" + +# 2. Presets: two button module presets (A, B) and a font group preset (G); B nests G. +presets_set "$(jq -cn --arg a "$PREFIX A" --arg b "$PREFIX B" --arg g "$PREFIX G" '{items: [ + {module: "button", name: $a, attrs: {"button.decoration.background": {desktop: {color: "#123456"}}}}, + {module: "button", name: $b, attrs: {"button.decoration.background": {desktop: {color: "#654321"}}}}, + {group: "font", name: $g, source_module: "button", + attrs: {"button.decoration.font.font": {desktop: {weight: "700"}}}}]}')" +PRESET_A=$(preset_id module button "$PREFIX A") +PRESET_B=$(preset_id module button "$PREFIX B") +PRESET_G=$(preset_id group font "$PREFIX G") +presets_set "$(jq -cn --arg b "$PREFIX B" --arg g "$PREFIX G" '{items: [ + {module: "button", preset: $b, group_presets: {font: $g}}]}')" +nested_groups "$PRESET_B" | jq -e --arg g "$PRESET_G" '."button.decoration.font".presetId == [$g]' >/dev/null \ + || fail "preset B does not nest group preset G" +pass "module presets A, B and group preset G created, B nests G" + +# 3. A stack and a group preset on page 1 through lit-divi-update. +TREE=$(tree_rev "$PAGE1") +UPDATE=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE1" --arg rev "$TREE" --arg n "$NODE" --arg a "$PREFIX A" \ + --arg b "$PREFIX B" --arg g "$PREFIX G" '{post_id: $id, tree_rev: $rev, changes: [ + {id: $n, preset: [$a, $b], group_presets: {font: $g}}]}')") +echo "$UPDATE" | jq -e '.ok == true and (.tree_rev | length) > 0 and ((.warnings // []) | map(select(contains("überdecken"))) | length) == 0' \ + >/dev/null || fail "update with stack and group preset: $UPDATE" +button_attrs "$PAGE1" | jq -e --arg a "$PRESET_A" --arg b "$PRESET_B" --arg g "$PRESET_G" \ + '.modulePreset == [$a, $b] and .groupPreset == {"button.decoration.font": {presetId: [$g], groupName: "divi/font"}}' >/dev/null \ + || fail "stored modulePreset/groupPreset of page 1: $(button_attrs "$PAGE1")" +OUTLINE=$(tool lit-divi-outline "{\"post_id\":$PAGE1}") +echo "$OUTLINE" | jq -e --arg n "$NODE" --arg g "font: $PREFIX G" \ + 'any(.nodes[]; .id == $n and ((.group_presets // []) | index($g) != null)) and (.outline | contains("Gruppen-Presets: " + $g))' \ + >/dev/null || fail "outline lacks the group preset: $OUTLINE" +pass "stack [A, B] and group preset stored; outline shows the group preset" + +# 4. Overrides: a differing local value is reported, clear_overrides removes it; a non-boolean value is refused. +TREE=$(tree_rev "$PAGE2") +UPDATE=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE2" --arg rev "$TREE" --arg n "$NODE" --arg a "$PREFIX A" \ + '{post_id: $id, tree_rev: $rev, changes: [ + {id: $n, preset: $a, attrs: {"button.decoration.background": {desktop: {color: "#abcdef"}}}}]}')") +echo "$UPDATE" | jq -e --arg n "$NODE" --arg a "$PREFIX A" \ + '.ok == true and ((.warnings // []) | map(select(contains($n) and contains("überdecken") and contains($a) and contains("clear_overrides"))) | length) == 1' \ + >/dev/null || fail "no override warning: $UPDATE" +button_attrs "$PAGE2" | jq -e --arg a "$PRESET_A" '.modulePreset == [$a] and .button.decoration.background != null' >/dev/null \ + || fail "page 2 should keep its local background: $(button_attrs "$PAGE2")" +TREE=$(echo "$UPDATE" | jq -r '.tree_rev') +BAD=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE2" --arg rev "$TREE" --arg n "$NODE" \ + '{post_id: $id, tree_rev: $rev, changes: [{id: $n, clear_overrides: "ja"}]}')") +# The input schema (type boolean) refuses it before the tool runs, so the error carries no tool code. +echo "$BAD" | jq -e 'has("__error") and (.__error | contains("clear_overrides"))' >/dev/null \ + || fail "non-boolean clear_overrides: $BAD" +CLEARED=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE2" --arg rev "$TREE" --arg n "$NODE" --arg a "$PREFIX A" \ + '{post_id: $id, tree_rev: $rev, changes: [{id: $n, preset: $a, clear_overrides: true}]}')") +echo "$CLEARED" | jq -e '.ok == true and ((.warnings // []) | map(select(contains("überdecken"))) | length) == 0' >/dev/null \ + || fail "clear_overrides: $CLEARED" +button_attrs "$PAGE2" | jq -e --arg a "$PRESET_A" '.modulePreset == [$a] and (.button.decoration.background // null) == null + and .button.innerContent.desktop.value.text == "Los"' >/dev/null \ + || fail "clear_overrides left or removed the wrong values: $(button_attrs "$PAGE2")" +pass "override warning, clear_overrides removes exactly the covering value, non-boolean refused by the schema" + +# 5. Mass assignment over both pages: stack [B] and group preset G. +ASSIGN=$(jq -cn --argjson p1 "$PAGE1" --argjson p2 "$PAGE2" --arg b "$PREFIX B" --arg g "$PREFIX G" \ + '{module: "button", preset: $b, group_presets: {font: $g}, post_ids: [$p1, $p2]}') +NONE=$(ability lit/divi-presets-assign "$(echo "$ASSIGN" | jq -c '. + {only_without_preset: true}')") +details "$NONE" | jq -e '(.pages_count // 0) == 0' >/dev/null || fail "only_without_preset should match no page: $NONE" +REVISIONS_BEFORE=$(revisions_of "$PAGE1") +DRY=$(timed "assign dry run" ability lit/divi-presets-assign "$ASSIGN") +[ "$(error_code "$DRY")" = "confirm_required" ] || fail "assign without confirm: $DRY" +details "$DRY" | jq -e --argjson p1 "$PAGE1" --argjson p2 "$PAGE2" \ + '.pages_count == 2 and .nodes_count == 2 and ([.pages[].post_id] | sort) == ([$p1, $p2] | sort) + and ((.blocked_ids // []) | length) == 0' >/dev/null || fail "assign dry run: $DRY" +button_attrs "$PAGE1" | jq -e --arg a "$PRESET_A" --arg b "$PRESET_B" '.modulePreset == [$a, $b]' >/dev/null \ + || fail "dry run changed page 1" +[ "$(revisions_of "$PAGE1")" = "$REVISIONS_BEFORE" ] || fail "dry run created a revision" +ASSIGNED=$(timed "assign confirmed" ability lit/divi-presets-assign "$(echo "$ASSIGN" | jq -c '. + {confirm: true}')") +echo "$ASSIGNED" | jq -e --argjson p1 "$PAGE1" --argjson p2 "$PAGE2" \ + '.ok == true and (.saved | length) == 2 and .remaining == 0 and ([.saved[].post_id] | sort) == ([$p1, $p2] | sort)' \ + >/dev/null || fail "assign with confirm: $ASSIGNED" +for id in "$PAGE1" "$PAGE2"; do + button_attrs "$id" | jq -e --arg b "$PRESET_B" --arg g "$PRESET_G" \ + '.modulePreset == [$b] and .groupPreset == {"button.decoration.font": {presetId: [$g], groupName: "divi/font"}}' \ + >/dev/null || fail "page $id after mass assignment: $(button_attrs "$id")" +done +REVISIONS_AFTER=$(revisions_of "$PAGE1") +AGAIN=$(ability lit/divi-presets-assign "$ASSIGN") +[ "$(error_code "$AGAIN")" != "confirm_required" ] || fail "second dry run still finds changes: $AGAIN" +details "$AGAIN" | jq -e '(.pages_count // 0) == 0' >/dev/null || fail "second dry run lists pages: $AGAIN" +[ "$(revisions_of "$PAGE1")" = "$REVISIONS_AFTER" ] || fail "second dry run created a revision" +pass "mass assignment: dry run lists 2 pages, confirm saved 2 (remaining 0), second dry run finds nothing" + +# 6. detach of the group preset: it is in use, a stale design_rev is refused, then dry run, confirm, delete. +GROUP_ITEM=$(jq -cn --arg g "$PRESET_G" '{items: [{group: "font", preset: $g}]}') +[ "$(error_code "$(ability lit/divi-presets-delete "$GROUP_ITEM")")" = "in_use" ] || fail "delete of a used group preset" +UNSUPPORTED=$(ability lit/divi-design-detach '{"variable": "Irgendeine Variable"}') +[ "$(error_code "$UNSUPPORTED")" = "unsupported" ] || fail "detach of a variable: $UNSUPPORTED" +DETACH=$(jq -cn --arg g "$PRESET_G" '{preset: {group: "font", preset: $g}}') +DRY=$(timed "detach dry run" ability lit/divi-design-detach "$DETACH") +[ "$(error_code "$DRY")" = "confirm_required" ] || fail "detach without confirm: $DRY" +DETACH_REV=$(details "$DRY" | jq -r '.design_rev') +details "$DRY" | jq -e '.pages_count == 2 and (.diff | length) > 0' >/dev/null || fail "detach dry run: $DRY" +STALE=$(ability lit/divi-design-detach "$(echo "$DETACH" | jq -c '. + {confirm: true, design_rev: "veraltet"}')") +[ "$(error_code "$STALE")" = "rev_mismatch" ] || fail "stale design_rev: $STALE" +nested_groups "$PRESET_B" | jq -e 'length > 0' >/dev/null || fail "stale detach changed preset B" +button_attrs "$PAGE1" | jq -e '(.groupPreset // null) != null' >/dev/null || fail "stale detach changed page 1" +pass "group preset is in use, variable detach unsupported, stale design_rev refused without writing" + +echo "info - preset B before detach: $(preset_item "$PRESET_B")" >&2 +DETACHED=$(timed "detach confirmed" ability lit/divi-design-detach "$(echo "$DETACH" | jq -c --arg r "$DETACH_REV" '. + {confirm: true, design_rev: $r}')") +echo "$DETACHED" | jq -e '.ok == true and (.saved | length) == 2 and .remaining == 0' >/dev/null \ + || fail "detach with confirm: $DETACHED" +nested_groups "$PRESET_B" | jq -e 'length == 0' >/dev/null || fail "preset B still nests the group preset" +echo "info - preset B after withoutNestedRef: $(preset_item "$PRESET_B")" >&2 +for id in "$PAGE1" "$PAGE2"; do + button_attrs "$id" | jq -e --arg b "$PRESET_B" '.modulePreset == [$b] and (.groupPreset // null) == null' >/dev/null \ + || fail "page $id after detach: $(button_attrs "$id")" +done +pass "detach removed the nested reference (snapshot pipeline) and the page references" + +DRY=$(ability lit/divi-presets-delete "$GROUP_ITEM") +[ "$(error_code "$DRY")" = "confirm_required" ] || fail "delete dry run after detach: $DRY" +DELETE_REV=$(details "$DRY" | jq -r '.design_rev') +DELETED=$(ability lit/divi-presets-delete "$(echo "$GROUP_ITEM" | jq -c --arg r "$DELETE_REV" '. + {confirm: true, design_rev: $r}')") +echo "$DELETED" | jq -e '.ok == true' >/dev/null || fail "delete after detach: $DELETED" +if preset_stored group divi/font "$PRESET_G"; then fail "group preset $PRESET_G is still stored"; fi +pass "group preset deletable after detach" + +finish