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) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 04:20:03 +02:00
co-authored by Claude Opus 5.5
parent 031f01451b
commit 1c2acdfbbe
3 changed files with 31 additions and 3 deletions
@@ -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(
+24 -3
View File
@@ -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')
@@ -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<string, mixed> */