diff --git a/php-transformer/composer.json b/php-transformer/composer.json index af3e9c21..952ec37e 100644 --- a/php-transformer/composer.json +++ b/php-transformer/composer.json @@ -37,6 +37,7 @@ "scripts": { "parity": "@test:parity", "static-parity": "php tools/static-parity/run.php", + "live-wp-parity": "php tools/live-wp-parity/run.php", "corpus-diagnostics": "php tools/corpus-diagnostics/run.php", "test": [ "@test:canonical", @@ -55,7 +56,8 @@ "php tests/unit/subtree-classifier.php", "php tests/unit/custom-block-generator.php", "php tests/unit/css-value-splitter.php", - "php tests/unit/corpus-detectors.php" + "php tests/unit/corpus-detectors.php", + "php tests/unit/live-wp-parity-runner.php" ], "test:parity": "php tests/parity/run.php", "test:migration:examples": "php tests/migration/examples.php", diff --git a/php-transformer/src/VisualParity/StaticStyleParityRunner.php b/php-transformer/src/VisualParity/StaticStyleParityRunner.php index 5d48c981..bfeab703 100644 --- a/php-transformer/src/VisualParity/StaticStyleParityRunner.php +++ b/php-transformer/src/VisualParity/StaticStyleParityRunner.php @@ -49,8 +49,51 @@ public function compareSourceToTransform(string $sourceHtml, string $authorCss = $result = $this->transformer->transform($sourceHtml, array())->toArray(); $candidateHtml = self::candidateHtmlFromSerializedBlocks((string) ($result['serialized_blocks'] ?? '')); - $sourceProbes = $this->probe->extract($sourceHtml, $authorCss); - $candidateProbes = $this->probe->extract($candidateHtml, $authorCss); + // The render-free proxy carries the SAME author CSS to both sides so the + // only variable is the transformed DOM. + return $this->compareSourceToCandidate($sourceHtml, $candidateHtml, $authorCss, $authorCss); + } + + /** + * Compare a source document against an EXTERNALLY produced candidate document. + * + * Unlike {@see compareSourceToTransform}, which builds the candidate render-free + * from serialized blocks, this entry accepts a candidate HTML string produced by + * a real WordPress render (e.g. the DOM HTML wp-codebox fetches after SSI import + * + activate). It runs the IDENTICAL deterministic probe + comparator so the + * report contract, score, and per-property diff are exactly the same shape as the + * render-free gate — only the candidate's provenance differs. + * + * This exercises WordPress's own block rendering + global-styles layer (the layer + * the render-free proxy cannot see) while staying fully deterministic: no browser, + * no rasterization, no network, no screenshots. The candidate's effective styling + * is resolved statically from whatever CSS the rendered DOM already carries + * (WP global-styles / block-supports / layout ` + +
Welcome aboard
+ Get started + +HTML; + +// A faithful "live-WP render": same content, same effective styling, but expressed +// the way WordPress would emit it (block wrapper classes + an inline global-styles +// + +
+
Welcome aboard
+ Get started +
+ +HTML; + +// A regressed "live-WP render": WP's rendering layer dropped the CTA background +// color and shifted the hero color — exactly the global-styles/block-supports +// regression class the render-free proxy cannot see. +$candidateRegressed = <<<'HTML' + + + +
+
Welcome aboard
+ Get started +
+ +HTML; + +// --------------------------------------------------------------------------- +// 1. Determinism: same inputs -> byte-identical report across two runs. +// --------------------------------------------------------------------------- +$run1 = $runner->compareSourceToCandidate($sourceHtml, $candidateRegressed); +$run2 = $runner->compareSourceToCandidate($sourceHtml, $candidateRegressed); +$json1 = json_encode($run1); +$json2 = json_encode($run2); +$assert($json1 === $json2, '1: external-candidate report is byte-identical across runs'); + +// --------------------------------------------------------------------------- +// 2. Wiring proof: feeding the render-free proxy's own candidate HTML through the +// external entry (same CSS both sides) reproduces compareSourceToTransform. +// --------------------------------------------------------------------------- +$proxyCss = '.hero { color: #112233; font-size: 24px; } .cta { background-color: #ff0000; }'; +$proxyReport = $runner->compareSourceToTransform($sourceHtml, $proxyCss); +$transformResult = (new \Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\HtmlTransformer()) + ->transform($sourceHtml, array())->toArray(); +$proxyCandidateHtml = StaticStyleParityRunner::candidateHtmlFromSerializedBlocks( + (string) ($transformResult['serialized_blocks'] ?? '') +); +$viaExternal = $runner->compareSourceToCandidate($sourceHtml, $proxyCandidateHtml, $proxyCss, $proxyCss); +$assert( + json_encode($proxyReport) === json_encode($viaExternal), + '2: external entry reproduces the render-free proxy when fed the proxy candidate + same CSS' +); + +// --------------------------------------------------------------------------- +// 3. Faithful live render scores perfectly (parity == 1.0, no findings). +// --------------------------------------------------------------------------- +$faithful = $runner->compareSourceToCandidate($sourceHtml, $candidateFaithful); +$faithfulScore = (float) ($faithful['parity']['score'] ?? 0.0); +$faithfulFindings = (int) ($faithful['summary']['finding_total'] ?? -1); +$assert($faithfulScore >= 0.999, '3: faithful live render reaches full parity', sprintf('score=%.4f', $faithfulScore)); +$assert(0 === $faithfulFindings, '3b: faithful live render yields no findings', sprintf('findings=%d', $faithfulFindings)); + +// --------------------------------------------------------------------------- +// 4. Regressed live render is caught and names the exact diverged properties. +// --------------------------------------------------------------------------- +$regressed = $runner->compareSourceToCandidate($sourceHtml, $candidateRegressed); +$regressedScore = (float) ($regressed['parity']['score'] ?? 1.0); +$assert($regressedScore < $faithfulScore, '4: regressed live render scores below faithful render', sprintf('regressed=%.4f faithful=%.4f', $regressedScore, $faithfulScore)); + +$divergedProperties = array(); +foreach ( (array) ($regressed['matches'] ?? array()) as $match ) { + foreach ( (array) ($match['style_deltas'] ?? array()) as $delta ) { + $divergedProperties[(string) ($delta['property'] ?? '')] = true; + } +} +$assert(isset($divergedProperties['background-color']), '4b: dropped CTA background-color is reported as a per-property diff'); +$assert(isset($divergedProperties['color']), '4c: shifted hero color is reported as a per-property diff'); + +// --------------------------------------------------------------------------- +// 5. Source author CSS must NOT leak onto the candidate side. If the source's CSS +// were applied to the candidate, the regressed candidate (whose own