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 @@ -95,6 +95,7 @@
"@test:parity",
"@test:packaging"
],
"test:canonical": "@test:scripts",
"test:scripts": [
"Composer\\Config::disableProcessTimeout",
"php tests/run.php"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,13 @@ public static function share(array $assets, AssetReferenceCanonicalizer $referen
unset($resource['stylesheet_instances'], $resource['stylesheet_parent'], $resource['frame_fallback'], $resource['reference_origin'], $resource['references']);
$resources[$path] = $resource;
}
// The original resource remains the enqueue owner, with no duplicated payload.
$assets[$index] = WordPressSitePlan::withRewrittenTextContent($asset, '');
$assets[$index]['stylesheet_parts'] = $paths;
// Keep occurrence ownership as metadata on a real resource, never
// as an empty CSS file that would also be published to disk.
$asset['stylesheet_parts'] = $paths;
unset($asset['content'], $asset['content_hash'], $asset['size_bytes']);
$resources[$paths[0]]['stylesheet_replays'][] = $asset;
unset($assets[$index]);
}
return array_merge($assets, array_values($resources));
return array_merge(array_values($assets), array_values($resources));
}
}
114 changes: 107 additions & 7 deletions php-transformer/src/WordPressSitePlan/WordPressSitePlan.php

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
$assert($normalize($states['index']['content']) === $normalize($states['about']['content']) && $states['index']['content'] !== $states['about']['content'], $case . ': editor-state CSS differs only in finalized declaration addresses.');
$assert($states['index']['path'] !== $states['about']['path'] && $states['index']['path'] === $states['copy']['path'], $case . ': different bytes diverge and identical bytes share a path.');
$stateAssets = array_values(array_filter($sitePlan['assets'], static fn (array $asset): bool => 'editor-static-state' === ($asset['source'] ?? '')));
$assert(2 === count($stateAssets), $case . ': identical final editor-state assets coalesce across routes.');
$assert(array() === $stateAssets, $case . ': editor-state sheets whose control selectors were removed with extracted chrome are not published as empty resources.');
}

// Use the public site's materialized theme presets and CSS, not compiler state.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
$nativeOrder = array_map(static fn (array $line): string => $line[2], $guarded);

$frontCondition = (string) (array_values(array_filter(array_keys($routes), static fn (string $condition): bool => str_contains($condition, 'is_front_page()')))[0] ?? '');
$aboutCondition = (string) (array_values(array_filter(array_keys($routes), static fn (string $condition): bool => str_contains($condition, "'about' === trim( get_page_uri")))[0] ?? '');
$aboutCondition = (string) (array_values(array_filter(array_keys($routes), static fn (string $condition): bool => str_contains($condition, "'about' === \$blocks_engine_page_uri")))[0] ?? '');
$assert('' !== $frontCondition && '' !== $aboutCondition && 2 === count($routes), 'Each canonical document keeps its own route block.');
foreach (array($frontCondition, $aboutCondition) as $condition) {
$files = $routes[$condition];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
$frontCondition = 'is_front_page()';
// Page routes match reconciliation identity first, with the canonical route
// path as fallback; locate each block by that route comparison.
$routeCondition = static fn(string $route): string => (string) (array_values(array_filter(array_keys($published['routes']), static fn(string $condition): bool => str_contains($condition, "'" . $route . "' === trim( get_page_uri( \$blocks_engine_page_id ), '/' )")))[0] ?? $route);
$routeCondition = static fn(string $route): string => (string) (array_values(array_filter(array_keys($published['routes']), static fn(string $condition): bool => str_contains($condition, "'" . $route . "' === \$blocks_engine_page_uri")))[0] ?? $route);
$aboutCondition = $routeCondition('guides/about');
$teamCondition = $routeCondition('guides/team');
foreach (array($frontCondition, $aboutCondition, $teamCondition) as $condition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@
$bootstrap = (string) ($siteWrites['functions.php']['payload']['data'] ?? '');
$bootstrap = \Automattic\BlocksEngine\PhpTransformer\Tests\Support\BootstrapStyles::expand($bootstrap);
$assert(array(array('kind' => 'global')) === ($siteAssets['assets/site.css']['scopes'] ?? null), 'Shared stylesheets retain an explicit global runtime scope.');
$assert('about.html' === ($siteAssets['assets/about.css']['scopes'][0]['source_path'] ?? null) && 1 === preg_match("~'about' === trim\\( get_page_uri\\( \\\$blocks_engine_page_id \\), '/' \\) \\) \\) \\{\\n \\\$blocks_engine_route_styles = true;\\n( wp_enqueue_style\\([^\\n]+\\n)* wp_enqueue_style\\( '[^']+', get_theme_file_uri\\( 'assets/assets/about\\.css' \\)~", $bootstrap) && 1 === substr_count($bootstrap, "get_theme_file_uri( 'assets/assets/about.css' )"), 'Page-owned stylesheets enqueue only inside their canonical WordPress route block, with the route comparison as the fallback after reconciliation identity.');
$assert('about.html' === ($siteAssets['assets/about.css']['scopes'][0]['source_path'] ?? null) && 1 === preg_match("~'about' === \\\$blocks_engine_page_uri \\) \\) \\{\\n \\\$blocks_engine_route_styles = true;\\n( wp_enqueue_style\\([^\\n]+\\n)* wp_enqueue_style\\( '[^']+', get_theme_file_uri\\( 'assets/assets/about\\.css' \\)~", $bootstrap) && 1 === substr_count($bootstrap, "get_theme_file_uri( 'assets/assets/about.css' )"), 'Page-owned stylesheets enqueue only inside their canonical WordPress route block, with the route comparison as the fallback after reconciliation identity.');
$assert('(min-width: 48rem)' === ($siteAssets['assets/about.css']['media'] ?? null) && str_contains($bootstrap, "array(), null, '(min-width: 48rem)'"), 'Stylesheet media conditions are retained as canonical frontend enqueue arguments.');
$assert(!str_starts_with((string) ($siteAssets['assets/about.css']['content'] ?? ''), '@media') && str_contains((string) ($siteAssets['assets/about.css']['content'] ?? ''), '.about-grid{display:grid') && '(min-width: 48rem)' === ($siteAssets['assets/about.css']['stylesheet_instances'][0]['media'] ?? null) && 'about.html' === ($siteAssets['assets/about.css']['stylesheet_instances'][0]['source_path'] ?? null), 'One stylesheet resource retains its original rule stream while its canonical document instance carries the enqueue condition.');
$assert(str_contains($bootstrap, "\$canvas = ! wp_should_load_block_editor_scripts_and_styles();") && str_contains($bootstrap, "( \$canvas || ( empty( \$style['author_css'] ) && empty( \$style['editor_only'] ) ) ) && \$blocks_engine_presentation_matches(") && ! str_contains($bootstrap, "add_editor_style(") && str_contains($bootstrap, "\$style['content_hash']") && str_contains($bootstrap, "\$style['media'] ?? 'all'") && str_contains($bootstrap, "get_theme_file_uri( \$style['target_path'] )"), 'Editor-only support styles and authored styles are enqueued by URL only on Core\'s editor-canvas iframe asset pass, route-scoped so an edited post loads only its own.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
$enqueues = (new ReflectionMethod(WordPressSitePlan::class, 'stylesheetEnqueues'))->invoke(null, $plan['assets'], $plan['pages']);
foreach ($enqueues as $enqueue) {
$asset = $enqueue['asset'];
$expected[] = array($asset['target_path'], $enqueue['scope'], $enqueue['handle'], $asset['source_media'] ?? $enqueue['media'], !empty($enqueue['fallback']), array());
foreach ($enqueue['scopes'] ?? array($enqueue['scope']) as $scope) $expected[] = array($asset['target_path'], $scope, $enqueue['handle'], $asset['source_media'] ?? $enqueue['media'], !empty($enqueue['fallback']), array());
}
if ($expected !== $actual) throw new RuntimeException('Presentation replay changed per-route enqueue behavior.');
foreach ($deltas as $delta) {
Expand Down
80 changes: 80 additions & 0 deletions php-transformer/tests/contract/support-owner-set-allocation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);

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

use Automattic\BlocksEngine\PhpTransformer\ArtifactCompiler\ArtifactCompiler;
use Automattic\BlocksEngine\PhpTransformer\WordPressSitePlan\WordPressSitePlan;

$assert = static function (bool $condition, string $message): void {
if (!$condition) throw new RuntimeException($message);
};
$files = array('site.css' => '.all-copy{width:601px;padding:13px}.subset-copy{width:602px;padding:14px}.unique-copy{width:603px;padding:15px}.other-subset{width:604px;padding:16px}');
$owners = array();
for ($i = 0; $i < 6; ++$i) {
$path = $i ? 'page-' . $i . '.html' : 'index.html';
$owners[] = $path;
$files[$path] = '<!doctype html><html><head><link rel="stylesheet" href="site.css"></head><body><main><div class="all-copy"><p>All pages</p></div>' . ($i < 3 ? '<div class="subset-copy"><p>Subset</p></div>' : '') . ($i >= 2 && $i <= 4 ? '<div class="other-subset"><p>Other subset</p></div>' : '') . (1 === $i ? '<div class="unique-copy"><p>Unique</p></div>' : '') . '</main></body></html>';
}
$result = (new ArtifactCompiler())->compile(array('entrypoint' => 'index.html', 'files' => $files))->toArray();
$plan = $result['source_reports']['wordpress_site_plan'];
WordPressSitePlan::assertValid($plan);
$support = array_values(array_filter($plan['assets'], static fn(array $asset): bool => 'engine-support' === ($asset['source'] ?? '')));
$bootstrap = '';
foreach ($plan['writes'] as $write) if ('functions.php' === $write['target_path']) $bootstrap = $write['payload']['data'];
echo 'Reduction: ' . count($support) . ' support files, ' . array_sum(array_map(static fn(array $asset): int => strlen($asset['content']), $support)) . ' support bytes, ' . strlen($bootstrap) . " functions.php bytes\n";
foreach (array('all-copy' => $owners, 'subset-copy' => array_slice($owners, 0, 3), 'other-subset' => array_slice($owners, 2, 3), 'unique-copy' => array($owners[1])) as $class => $consumers) {
$rule = '.' . $class . '{box-sizing:content-box}';
$sheets = array_values(array_filter($support, static fn(array $asset): bool => str_contains($asset['content'], $rule)));
$assert(1 === count($sheets), 'Each generated rule is written exactly once: ' . $class);
$actual = array_column($sheets[0]['scopes'], 'source_path');
sort($actual);
sort($consumers);
$assert(count($consumers) === count($owners) ? array(array('kind' => 'global')) === $sheets[0]['scopes'] : $actual === $consumers, 'Resource scopes equal exact rule consumers: ' . $class);
}
$globalSheets = array_values(array_filter($support, static fn(array $asset): bool => array(array('kind' => 'global')) === $asset['scopes']));
$assert(1 === count($globalSheets), 'All-pages rules share one global resource.');
$assert(str_contains($bootstrap, "if ( true )"), 'Global support also loads on WordPress-native routes.');
$assert(str_contains($bootstrap, "in_array( \$blocks_engine_page_uri, ['page-2','page-3','page-4'], true )"), 'A subset membership condition includes exactly its three owning routes.');
$seen = array();
foreach ($support as $asset) {
$assert('' !== trim($asset['content']), 'Support resources are never empty.');
if (1 === count($asset['scopes']) && 'global' !== $asset['scopes'][0]['kind']) $assert($owners[1] === $asset['scopes'][0]['source_path'], 'Pages without unique rules have no unique resource.');
$tree = new Automattic\BlocksEngine\PhpTransformer\Css\CssRuleTree($asset['content']);
foreach ($tree->rules() as $row) {
$rule = trim(substr($asset['content'], $row['start'], $row['end'] - $row['start'] + 1));
$assert(!isset($seen[$rule]), 'No support rule is written twice.');
$seen[$rule] = true;
}
}
foreach ($plan['assets'] as $asset) if (in_array($asset['source'] ?? '', array('engine-support', 'editor-static-state'), true)) {
$assert('' !== trim($asset['content']), 'Generated CSS resources are never empty.');
$writes = array_column($plan['writes'], null, 'target_path');
$assert('' !== trim($writes[$asset['target_path']]['payload']['data'] ?? ''), 'Generated CSS writes are never empty.');
}
$byTarget = array_column($plan['assets'], null, 'target_path');
foreach ($owners as $owner) {
$phase = 0;
foreach (WordPressSitePlan::documentStylesheets($plan, $owner) as $sheet) {
$asset = $byTarget[$sheet['target_path']];
$next = 'engine-support' !== ($asset['source'] ?? '') ? 1 : ('before-author' === ($asset['stylesheet_placement'] ?? '') ? 0 : 2);
$assert($next >= $phase, 'Document cascade remains before support, author, after support.');
$phase = $next;
if ('engine-support' === ($asset['source'] ?? '')) $assert(in_array('global', array_column($asset['scopes'], 'kind'), true) || in_array($owner, array_column($asset['scopes'], 'source_path'), true), 'No unrelated support loads.');
}
}
preg_match("/add_action\( 'wp_enqueue_scripts', static function \(\): void \{(.*?)\n\}, 1 \);/s", $bootstrap, $callback);
$assert(1 === substr_count($callback[1] ?? '', WordPressSitePlan::DOCUMENT_ID_EXPRESSION), 'Styles resolve the current document exactly once.');
$realisticFiles = require __DIR__ . '/support-owner-set-realistic-fixture.php';
$realistic = (new ArtifactCompiler())->compile(array('entrypoint' => 'index.html', 'files' => $realisticFiles))->toArray()['source_reports']['wordpress_site_plan'];
WordPressSitePlan::assertValid($realistic);
$writes = array_column($realistic['writes'], null, 'target_path');
$repairs = 0;
foreach ($realistic['assets'] as $asset) {
if (!in_array($asset['source'] ?? '', array('engine-support', 'editor-static-state'), true)) continue;
if ('editor-static-state' === $asset['source']) ++$repairs;
$assert('' !== trim($asset['content']), 'Realistic support and repair resources are never empty.');
$assert('' !== trim($writes[$asset['target_path']]['payload']['data'] ?? ''), 'Realistic support and repair writes are never empty.');
}
$assert($repairs > 0, 'The realistic fixture exercises editor repair sharing.');
echo "Support owner-set allocation passed\n";
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);

// Six-page brochure site: shared chrome, two product families, unique content,
// responsive author CSS, and an uncovered anchor requiring editor repair.
$files = array('site.css' => 'body{margin:0;color:#243040;font-family:sans-serif}.site-nav{padding:24px;width:960px}.site-footer{padding:32px;width:960px}.hero{width:920px;padding:40px}.product-card{width:420px;padding:20px}.service-card{width:620px;padding:28px}#details{padding:13px}@media(max-width:700px){.hero{width:90%;padding:20px}}');
foreach (array('index', 'products', 'pricing', 'services', 'team', 'contact') as $index => $slug) {
$unique = 'feature-' . $slug;
$files['site.css'] .= '.' . $unique . '{width:' . (700 + $index) . 'px;padding:' . (16 + $index) . 'px}';
$family = $index < 3 ? 'product-card' : 'service-card';
$files[$slug . '.html'] = '<!doctype html><html><head><title>' . ucfirst($slug) . '</title><link rel="stylesheet" href="site.css"></head><body><header class="site-nav"><nav><a href="index.html">Home</a><a href="products.html">Products</a><a href="services.html">Services</a><a href="contact.html">Contact</a></nav></header><main><section class="hero"><h1>' . ucfirst($slug) . '</h1><p>Practical products and services for growing teams.</p></section><section class="' . $family . '"><h2>Built for your business</h2><p>Discover our approach and explore the options.</p></section><section class="' . $unique . '"><h2>More information</h2><a href="#details">Read details</a><div id="details"><p>Talk to our team about your next project.</p></div></section></main><footer class="site-footer"><p>Example Company</p><a href="contact.html">Get in touch</a></footer></body></html>';
}
return $files;
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@
$assert(array('services.html', 'portfolio.html') === $pageSubsetScopes, 'a stylesheet linked by a subset of pages retains every owning page scope without becoming global');
$pageSubsetWrites = array_column($pageSubsetStylesheet['source_reports']['wordpress_site_plan']['writes'] ?? array(), null, 'target_path');
$pageSubsetBootstrap = (string) ($pageSubsetWrites['functions.php']['payload']['data'] ?? '');
$assert(str_contains($pageSubsetBootstrap, "'services' === trim( get_page_uri( \$blocks_engine_page_id ), '/' )") && str_contains($pageSubsetBootstrap, "'portfolio' === trim( get_page_uri( \$blocks_engine_page_id ), '/' )"), 'a subset-scoped stylesheet bootstrap matches every owning WordPress route');
$assert(str_contains($pageSubsetBootstrap, "'services' === \$blocks_engine_page_uri") && str_contains($pageSubsetBootstrap, "'portfolio' === \$blocks_engine_page_uri"), 'a subset-scoped stylesheet bootstrap matches every owning WordPress route');
$assert(array(array('kind' => 'global')) === ($pageSubsetAssets['explicit-shared.css']['scopes'] ?? null), 'explicitly shared linked stylesheets retain global scope');
$assert('asset' === ($pageSubsetAssets['unreferenced.css']['kind'] ?? null) && 'source-provenance' === ($pageSubsetAssets['unreferenced.css']['role'] ?? null), 'unreferenced author stylesheets are retained as provenance, not active CSS');
$pageSubsetCompiledAssets = array_column($pageSubsetStylesheet['source_reports']['compiled_site']['assets'] ?? array(), null, 'path');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
foreach ($plan['writes'] as $write) {
if ('functions.php' === ($write['target_path'] ?? null)) $bootstrap = BootstrapStyles::expand((string) $write['payload']['data']);
}
foreach (array('is_front_page()', "'about' === trim( get_page_uri( \$blocks_engine_page_id ), '/'") as $route) {
foreach (array('is_front_page()', "'about' === \$blocks_engine_page_uri") as $route) {
$inRoute = false;
$linked = false;
foreach (explode("\n", $bootstrap) as $line) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
$containing = static fn(string $needle): callable => static fn(string $target): bool => str_contains($contents[$target] ?? '', $needle);
$named = static fn(string $name): callable => static fn(string $target): bool => str_ends_with($target, '/' . $name);

foreach (array('home' => 'if ( is_front_page() ) {', 'post' => "'post' === trim( get_page_uri( \$blocks_engine_page_id ), '/'") as $route => $needle) {
foreach (array('home' => 'if ( is_front_page() ) {', 'post' => "'post' === \$blocks_engine_page_uri") as $route => $needle) {
$targets = $routeEnqueues($needle);
$base = $position($targets, $named('base.css'));
$inline = $position($targets, $containing('padding:56px'));
Expand All @@ -76,7 +76,7 @@
$assert($base < $inline && $inline < $responsive, "{$route}: inline <style> is enqueued between the links that surround it", implode(' -> ', $targets));
}

$split = $routeEnqueues("'split' === trim( get_page_uri( \$blocks_engine_page_id ), '/'");
$split = $routeEnqueues("'split' === \$blocks_engine_page_uri");
$before = $position($split, $containing('margin:8px'));
$responsive = $position($split, $named('responsive.css'));
$after = $position($split, $containing('margin:4px'));
Expand Down
Loading