Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions php-transformer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@
"php tests/unit/corpus-detectors.php",
"php tests/unit/live-wp-parity-runner.php",
"php tests/unit/deterministic-row-deduplicator.php",
"php tests/unit/conversion-report-projection-input-reuse.php",
"php tests/unit/html-transformer-shared-analysis-cache.php",
"php tests/unit/html-transformer-session-state.php",
"php tests/unit/reusable-component-recognition.php",
Expand Down
34 changes: 20 additions & 14 deletions php-transformer/src/Contract/ConversionReportProjection.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,28 @@ final class ConversionReportProjection
*/
public static function fromResultParts(string $sourceFormat, array $blocks, array $fallbacks, array $sourceReports, array $assets, array $provenance, array $metrics): array
{
$fallbackDiagnostics = self::fallbackDiagnostics($fallbacks);
$runtimeIslands = self::runtimeIslands($sourceReports);
$runtimeIslandSummaryEntries = self::runtimeIslandSummaryEntries($runtimeIslands);

$report = array(
'schema' => self::SCHEMA,
'finding_schema' => ConversionFindingContract::SCHEMA,
'source_format' => $sourceFormat,
'source' => self::firstString($provenance, 'source'),
'scope' => self::firstString($provenance, 'scope'),
'source_summary' => self::sourceSummary($sourceFormat, $blocks, $fallbacks, $sourceReports, $assets, $metrics),
'selector_summary' => self::selectorSummary($sourceReports, $fallbacks),
'conversion_classification_summary' => self::conversionClassificationSummary($sourceReports, $fallbacks),
'fallback_diagnostics' => self::fallbackDiagnostics($fallbacks),
'selector_summary' => self::selectorSummary($sourceReports, $fallbackDiagnostics, $runtimeIslandSummaryEntries),
'conversion_classification_summary' => self::conversionClassificationSummary($sourceReports, $fallbackDiagnostics, $runtimeIslandSummaryEntries),
'fallback_diagnostics' => $fallbackDiagnostics,
'core_html_fallback_evidence' => self::coreHtmlFallbackEvidence($sourceReports),
'asset_refs' => self::assetReferences($blocks, $sourceReports),
'navigation_candidates' => self::navigationCandidates($blocks, $sourceReports),
'semantic_parity' => self::semanticParity($sourceReports),
'editability_report' => is_array($sourceReports['editability_report'] ?? null) ? $sourceReports['editability_report'] : array(),
'editability_policy' => is_array($sourceReports['editability_policy'] ?? null) ? $sourceReports['editability_policy'] : array(),
'runtime_dependency_parity' => self::runtimeDependencyParity($sourceReports),
'runtime_islands' => self::runtimeIslands($sourceReports),
'runtime_islands' => $runtimeIslands,
'interaction_candidates' => self::interactionCandidates($sourceReports),
'presentation_gaps' => self::presentationGaps($sourceReports),
'native_target_blocks' => self::stringList($sourceReports, 'native_target_blocks'),
Expand Down Expand Up @@ -89,10 +93,11 @@ private static function sourceSummary(string $sourceFormat, array $blocks, array

/**
* @param array<string, mixed> $sourceReports
* @param array<int, array<string, mixed>> $fallbacks
* @param array<int, array<string, mixed>> $fallbackDiagnostics
* @param array<int, array<string, mixed>> $runtimeIslandSummaryEntries
* @return array<string, mixed>
*/
private static function selectorSummary(array $sourceReports, array $fallbacks): array
private static function selectorSummary(array $sourceReports, array $fallbackDiagnostics, array $runtimeIslandSummaryEntries): array
{
$selectors = array();
$sources = array();
Expand All @@ -102,12 +107,12 @@ private static function selectorSummary(array $sourceReports, array $fallbacks):
self::appendSourcePath($sources, $entry);
}

foreach ( self::fallbackDiagnostics($fallbacks) as $entry ) {
foreach ( $fallbackDiagnostics as $entry ) {
self::appendSelector($selectors, $entry, 'fallback');
self::appendSourcePath($sources, $entry);
}

foreach ( self::runtimeIslandSummaryEntries($sourceReports) as $entry ) {
foreach ( $runtimeIslandSummaryEntries as $entry ) {
self::appendSelector($selectors, $entry, 'runtime_island');
self::appendSourcePath($sources, $entry);
}
Expand Down Expand Up @@ -196,15 +201,16 @@ private static function fallbackDiagnostics(array $fallbacks): array

/**
* @param array<string, mixed> $sourceReports
* @param array<int, array<string, mixed>> $fallbacks
* @param array<int, array<string, mixed>> $fallbackDiagnostics
* @param array<int, array<string, mixed>> $runtimeIslandSummaryEntries
* @return array<string, mixed>
*/
private static function conversionClassificationSummary(array $sourceReports, array $fallbacks): array
private static function conversionClassificationSummary(array $sourceReports, array $fallbackDiagnostics, array $runtimeIslandSummaryEntries): array
{
$byClassification = array();
$byStrategy = array();

foreach ( array_merge(self::sourceProvenance($sourceReports), self::fallbackDiagnostics($fallbacks), self::runtimeIslandSummaryEntries($sourceReports)) as $entry ) {
foreach ( array_merge(self::sourceProvenance($sourceReports), $fallbackDiagnostics, $runtimeIslandSummaryEntries) as $entry ) {
if ( ! is_array($entry) ) {
continue;
}
Expand Down Expand Up @@ -370,13 +376,13 @@ private static function runtimeIslands(array $sourceReports): array
}

/**
* @param array<string, mixed> $sourceReports
* @param array<int, array<string, mixed>> $runtimeIslands
* @return array<int, array<string, mixed>>
*/
private static function runtimeIslandSummaryEntries(array $sourceReports): array
private static function runtimeIslandSummaryEntries(array $runtimeIslands): array
{
$entries = array();
foreach ( self::runtimeIslands($sourceReports) as $island ) {
foreach ( $runtimeIslands as $island ) {
$entries[] = array_filter(
array(
'selector' => $island['selector'] ?? '',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);

require dirname(__DIR__, 2) . '/vendor/autoload.php';

use Automattic\BlocksEngine\PhpTransformer\Contract\ConversionReportProjection;

$failures = 0;
$assert = static function (bool $condition, string $message) use (&$failures): void {
if ( ! $condition ) {
++$failures;
fwrite(STDERR, "FAIL: {$message}\n");
}
};

$fallbacks = array(
array('diagnostic_code' => 'html_script_fallback', 'selector' => '#widget', 'source_path' => 'index.html', 'conversion_classification' => 'runtime_gap', 'preservation_strategy' => 'core_html'),
array('diagnostic_code' => 'html_unsafe_inline_svg', 'selector' => '.logo svg', 'source_path' => 'about.html', 'conversion_classification' => 'asset_gap', 'preservation_strategy' => 'materialize_asset'),
);
$sharedIsland = array('selector' => '#widget', 'source_path' => 'index.html', 'tag' => 'div', 'preservation_strategy' => 'scoped_runtime_metadata');
$secondIsland = array('selector' => '.map', 'source_path' => 'about.html', 'tag' => 'section', 'disposition' => 'preserved');
$report = ConversionReportProjection::fromResultParts('html', array(), $fallbacks, array(
'html' => array(
'source_provenance' => array(array('selector' => 'main', 'source_path' => 'index.html', 'conversion_classification' => 'native', 'preservation_strategy' => 'native_block')),
'runtime_islands' => array($sharedIsland, $secondIsland),
),
'runtime_islands' => array($sharedIsland),
), array(), array(), array());

$assert(
array($sharedIsland, $secondIsland) === ($report['runtime_islands'] ?? null),
'runtime islands retain first-occurrence ordering while overlapping declarations are deduplicated'
);
$assert(
array('main', '#widget', '.logo svg', '#widget', '.map') === array_column($report['selector_summary']['selectors'] ?? array(), 'selector'),
'selector summary retains source, fallback, and deduplicated runtime-island order'
);
$assert(
array('index.html', 'about.html') === ($report['selector_summary']['source_paths'] ?? null),
'selector summary source paths retain their first occurrence order'
);
$assert(
array('native' => 1, 'runtime_gap' => 1, 'asset_gap' => 1, 'runtime_island_preserved' => 2) === ($report['conversion_classification_summary']['by_classification'] ?? null),
'classification summary counts each normalized fallback and runtime island exactly once'
);
$assert(
array('native_block' => 1, 'core_html' => 1, 'materialize_asset' => 1, 'scoped_runtime_metadata' => 2) === ($report['conversion_classification_summary']['by_preservation_strategy'] ?? null),
'preservation strategy summary preserves normalized row counts and omission behavior'
);
$assert(
array('html_script_fallback', 'html_unsafe_inline_svg') === array_column($report['fallback_diagnostics'] ?? array(), 'diagnostic_code'),
'fallback diagnostics retain normalized final rows in input order'
);

if ( 0 < $failures ) {
exit(1);
}

echo "conversion-report-projection-input-reuse ok\n";
Loading