From 1c2acdfbbee9f042a26ca3ae702beb19cdd78b63 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sun, 27 Sep 2026 04:20:03 +0200 Subject: [PATCH] fix: node-copy from a library entry unifies CRLF; smoke seeds a real CRLF entry (M-1, M-2/M-19) Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Divi/Pages/DiviNodeCopyAbility.php | 5 ++++ tests/Integration/divi-library-smoke.sh | 27 ++++++++++++++++--- .../Divi/Pages/DiviNodeCopyAbilityTest.php | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Integrations/Divi/Pages/DiviNodeCopyAbility.php b/src/Integrations/Divi/Pages/DiviNodeCopyAbility.php index 148dddc..ec99827 100644 --- a/src/Integrations/Divi/Pages/DiviNodeCopyAbility.php +++ b/src/Integrations/Divi/Pages/DiviNodeCopyAbility.php @@ -139,6 +139,11 @@ final class DiviNodeCopyAbility extends DiviWriteAbility } $copy = $node->copy(); + if ($source->isLibraryItem()) { + // Finding 9: library entries from Divi's dialog use CRLF; unified before every takeover + // (LibraryContent::load), as lit-library-insert and lit/library-save do. + $copy->unifyLineBreaks(); + } $problems = $composition->storedGuard()->problems($copy); if ($problems !== []) { return Errors::invalidInput( diff --git a/tests/Integration/divi-library-smoke.sh b/tests/Integration/divi-library-smoke.sh index d61c376..73c65c8 100755 --- a/tests/Integration/divi-library-smoke.sh +++ b/tests/Integration/divi-library-smoke.sh @@ -212,7 +212,20 @@ echo "$LISTED" | jq -e --argjson l "$LIB_ID" --argjson g "$GLOB_ID" ' ability lit/library-list "{\"id\":$GLOB_ID}" | jq -e '.outline | length > 0' >/dev/null || fail "library-list detail of $GLOB_ID" pass "lit/library-save global: bytes, terms and meta like Divi's entry; lit/library-list shows both" -# 4. Insert the global entry as instance, the normal entry as copy: bytes as the Visual Builder writes them +# A normal section entry with the CRLF bytes of Divi's library dialog (stage3 fixture as-is, finding 9). +CRLF_ID=$(LIT_FILE="$STAGE3/library--library-abschnitt.html" LIT_TITLE="$PREFIX CRLF" wp --user="$ADMIN_LOGIN" eval ' +$id = wp_insert_post(["post_type" => "et_pb_layout", "post_status" => "publish", "post_title" => getenv("LIT_TITLE"), + "post_content" => wp_slash((string) file_get_contents(getenv("LIT_FILE")))]); +wp_set_object_terms($id, "section", "layout_type"); +wp_set_object_terms($id, "regular", "module_width"); +wp_set_object_terms($id, "non_global", "scope"); +update_post_meta($id, "_et_pb_template_type", "section"); +update_post_meta($id, "_et_pb_built_for_post_type", "page"); +echo $id;') +CREATED_POSTS+=("$CRLF_ID") +content_of "$CRLF_ID" | grep -q $'\r' || fail "setup: the CRLF entry holds no CR" + +# 4. Insert the global entry as instance, the CRLF entry as copy: bytes as the Visual Builder writes them BEFORE=$(content_of "$TARGET_ID") INST=$(tool lit-library-insert "$(jq -cn --argjson id "$TARGET_ID" --arg rev "$(rev_of "$TARGET_ID")" --argjson lib "$GLOB_ID" \ '{post_id: $id, tree_rev: $rev, library_id: $lib, parent: "s0.r0.c0", index: 0}')") @@ -222,7 +235,7 @@ EXPECTED_INSTANCE=$(snippet instance | sed "s/\"globalModule\":\"1059\"/\"global || fail "instance bytes differ from the Visual Builder's: $(content_of "$TARGET_ID")" outline "$TARGET_ID" | jq -e --arg g "$GLOB_ID" '.outline | contains($g)' >/dev/null || fail "outline does not name the instance" BEFORE=$(content_of "$TARGET_ID") -COPY=$(tool lit-library-insert "$(jq -cn --argjson id "$TARGET_ID" --arg rev "$(echo "$INST" | jq -r .tree_rev)" --argjson lib "$LIB_ID" \ +COPY=$(tool lit-library-insert "$(jq -cn --argjson id "$TARGET_ID" --arg rev "$(echo "$INST" | jq -r .tree_rev)" --argjson lib "$CRLF_ID" \ '{post_id: $id, tree_rev: $rev, library_id: $lib, mode: "copy"}')") echo "$COPY" | jq -e '.ok == true and .changed[0].id == "s1"' >/dev/null || fail "library-insert copy: $COPY" [ "$(inserted_once "$TARGET_ID" "$BEFORE" "$SECTION_BYTES")" = "yes" ] || fail "copy bytes differ from the entry's section" @@ -269,7 +282,15 @@ NODE_COPY=$(ability lit/divi-node-copy "$(jq -cn --argjson id "$TARGET_ID" --arg echo "$NODE_COPY" | jq -e '.ok == true and .revision_id != null' >/dev/null || fail "node-copy: $NODE_COPY" [ "$(inserted_once "$TARGET_ID" "$BEFORE" "$SECTION_BYTES")" = "yes" ] || fail "node-copy bytes differ from the source node" [ "$(content_hash "$SOURCE_ID")" = "$SOURCE_HASH" ] || fail "node-copy changed the source page" -pass "lit/divi-node-copy: stale source_tree_rev refused, copy byte-exact, source unchanged" +BEFORE=$(content_of "$TARGET_ID") +LIB_COPY=$(ability lit/divi-node-copy "$(jq -cn --argjson id "$TARGET_ID" --arg rev "$(echo "$NODE_COPY" | jq -r .tree_rev)" \ + --argjson src "$CRLF_ID" --arg srev "$(rev_of "$CRLF_ID")" \ + '{post_id: $id, tree_rev: $rev, source_post_id: $src, source_id: "s0", source_tree_rev: $srev}')") +echo "$LIB_COPY" | jq -e '.ok == true' >/dev/null || fail "node-copy from the CRLF entry: $LIB_COPY" +[ "$(inserted_once "$TARGET_ID" "$BEFORE" "$SECTION_BYTES")" = "yes" ] || fail "node-copy from a library entry: bytes differ" +if content_of "$TARGET_ID" | grep -q $'\r'; then fail "node-copy brought CRLF into the page"; fi +NODE_COPY=$LIB_COPY +pass "lit/divi-node-copy: stale source_tree_rev refused, copy byte-exact (CRLF unified from a library entry), source unchanged" # 7. {{link:ID}} in a button URL: Divi's post_link_url_page; a draft target renders only ?page_id= TREV=$(echo "$NODE_COPY" | jq -r '.tree_rev') diff --git a/tests/Unit/Integrations/Divi/Pages/DiviNodeCopyAbilityTest.php b/tests/Unit/Integrations/Divi/Pages/DiviNodeCopyAbilityTest.php index 21c1aaa..7ad4241 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviNodeCopyAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviNodeCopyAbilityTest.php @@ -203,6 +203,8 @@ final class DiviNodeCopyAbilityTest extends TestCase self::assertTrue($result['ok']); self::assertStringContainsString('Library-Blurb', $this->saved[0]); + self::assertStringContainsString(chr(13), (string) $item->post_content, 'the entry uses CRLF'); + self::assertStringNotContainsString(chr(13), $this->saved[0], 'line breaks unified like LibraryContent::load'); } /** @return array */