Files
janniclas.loosenandClaude Opus 5.5 4e959833b9 fix: identical means identical bytes – text parts count, pages and posts only (ruling Q25)
- Fingerprint::content() includes non-whitespace text parts (raw HTML between
  blocks, inner HTML of core blocks) and opaque blocks with all their bytes;
  structure() unchanged; find-similar "identical" follows
- make-global mode 2 also compares Fingerprint::canonical() bytes of each node
- sources and targets: post types page and post only (POST_TYPES)
- a rolled-back page N>1 keeps DiviSaver's "Revision X bleibt erhalten"
- guide: escaped divi/global-layout re-expanded by Divi's do_blocks in
  shortcode-module and WooCommerce text output documented as residual risk

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-27 21:59:20 +02:00

136 lines
5.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\BlockTree;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\BlockTree\Fingerprint;
use LIT\MCP\Integrations\Divi\BlockTree\LibraryContent;
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
use LIT\MCP\Tests\Support\DiviFixtures;
use PHPUnit\Framework\TestCase;
final class FingerprintTest extends TestCase
{
/** The section of library entry 1055: section > row > column (24_24) > blurb. */
private static function section(): Block
{
$content = DiviFixtures::markup('stage3/library--library-abschnitt.html');
return LibraryContent::roots(LibraryContent::load(new Parser(), $content))[0];
}
private static function blurbOf(Block $section): Block
{
return $section->children()[0]->children()[0]->children()[0];
}
public function testAdminLabelsAndBuilderVersionDoNotCount(): void
{
$a = self::section();
$b = $a->copy();
$attrs = $b->attrs();
$attrs['module']['meta']['adminLabel']['desktop']['value'] = 'Anderes Label';
$attrs['builderVersion'] = '5.3.0';
$b->setAttrs($attrs);
self::assertSame(16, strlen(Fingerprint::content($a)));
self::assertSame(Fingerprint::content($a), Fingerprint::content($b));
self::assertSame(Fingerprint::structure($a), Fingerprint::structure($b));
}
public function testOtherTextsKeepTheStructureButNotTheContent(): void
{
$a = self::section();
$b = $a->copy();
$blurb = self::blurbOf($b);
$attrs = $blurb->attrs();
$attrs['title']['innerContent']['desktop']['value'] = 'Anderer Titel';
$blurb->setAttrs($attrs);
self::assertSame(Fingerprint::structure($a), Fingerprint::structure($b));
self::assertNotSame(Fingerprint::content($a), Fingerprint::content($b));
}
public function testColumnLayoutAndModuleTypesChangeTheStructure(): void
{
$a = self::section();
$b = $a->copy();
$column = $b->children()[0]->children()[0];
$attrs = $column->attrs();
$attrs['module']['decoration']['sizing']['desktop']['value']['flexType'] = '12_24';
$column->setAttrs($attrs);
$c = Block::create('divi/section', [], [Block::create('divi/row', [], [Block::create('divi/column', [], [
Block::create('divi/text'),
])])]);
self::assertNotSame(Fingerprint::structure($a), Fingerprint::structure($b));
self::assertNotSame(Fingerprint::structure($a), Fingerprint::structure($c));
}
/** The section of 1055 with $extra inserted at the end of its column (review of Task 15, ruling Q25). */
private static function sectionWith(string $extra): Block
{
$markup = DiviFixtures::markup('stage3/library--library-abschnitt.html');
$at = strrpos($markup, '<!-- /wp:divi/column -->');
$markup = substr($markup, 0, (int) $at) . $extra . substr($markup, (int) $at);
return LibraryContent::roots(LibraryContent::load(new Parser(), $markup))[0];
}
/** @return array<string, array{string, string}> */
public static function visibleExtras(): array
{
return [
'raw html between blocks' => ['<p>ONLY ON B</p>', '<p>ONLY ON C</p>'],
'core paragraph' => [
'<!-- wp:paragraph --><p>ONLY ON B</p><!-- /wp:paragraph -->',
'<!-- wp:paragraph --><p>ONLY ON C</p><!-- /wp:paragraph -->',
],
'divi 4 shortcode text' => ['[et_pb_text]ONLY ON B[/et_pb_text]', '[et_pb_text]ONLY ON C[/et_pb_text]'],
];
}
/**
* Ruling Q25: text parts are visible – raw HTML between blocks and the inner HTML of core blocks count
* for the content, like attributes.
*
* @dataProvider visibleExtras
*/
public function testVisibleTextPartsCountForTheContent(string $extra, string $other): void
{
$plain = self::section();
self::assertNotSame(Fingerprint::content($plain), Fingerprint::content(self::sectionWith($extra)));
self::assertNotSame(
Fingerprint::content(self::sectionWith($extra)),
Fingerprint::content(self::sectionWith($other))
);
self::assertNotSame(Fingerprint::canonical($plain), Fingerprint::canonical(self::sectionWith($extra)));
}
public function testRawTextKeepsTheStructureAndWhitespaceNeverCounts(): void
{
$plain = self::section();
self::assertSame(Fingerprint::structure($plain), Fingerprint::structure(self::sectionWith('<p>x</p>')));
self::assertSame(Fingerprint::content($plain), Fingerprint::content(self::sectionWith("\n \n")));
self::assertSame(Fingerprint::canonical($plain), Fingerprint::canonical(self::sectionWith("\n\t")));
}
/** Opaque blocks count with all their bytes (a global instance's snapshot included). */
public function testOpaqueBlocksCountWithAllTheirBytes(): void
{
$instance = static fn (string $text): Block => LibraryContent::roots(LibraryContent::load(
new Parser(),
'<!-- wp:divi/placeholder --><!-- wp:divi/section --><!-- wp:divi/row --><!-- wp:divi/column -->'
. '<!-- wp:divi/global-layout {"globalModule":"5","blockName":"divi/text"} --><p>' . $text
. '</p><!-- /wp:divi/global-layout -->'
. '<!-- /wp:divi/column --><!-- /wp:divi/row --><!-- /wp:divi/section --><!-- /wp:divi/placeholder -->'
))[0];
self::assertNotSame(Fingerprint::content($instance('a')), Fingerprint::content($instance('b')));
}
}