diff --git a/php-transformer/composer.json b/php-transformer/composer.json index 93375ebe9..0051bd6d9 100644 --- a/php-transformer/composer.json +++ b/php-transformer/composer.json @@ -348,6 +348,7 @@ "php tests/unit/captured-dialog-open-state.php", "php tests/unit/captured-dialog-dropdown-panel.php", "php tests/unit/captured-dialog-shared-header.php", + "php tests/unit/captured-dialog-ordinal-shared-header.php", "php tests/unit/captured-dialog-editor-at-rest.php", "php tests/unit/scoped-mixed-dropdown.php", "php tests/unit/capture-runtime-ancestor-parity.php", diff --git a/php-transformer/src/WordPressSitePlan/ShellExtraction.php b/php-transformer/src/WordPressSitePlan/ShellExtraction.php index d56cd43ee..cf55227e8 100644 --- a/php-transformer/src/WordPressSitePlan/ShellExtraction.php +++ b/php-transformer/src/WordPressSitePlan/ShellExtraction.php @@ -2708,7 +2708,9 @@ private static function normalizeNestedChromeMarkup(string $markup): string $markup = self::withoutScrollStateCarrierIdentity($markup); $markup = preg_replace('/\s*' . EngineMarker::patternBody() . '/', '', $markup) ?? $markup; $markup = preg_replace('/\s*(?:' . EngineMarker::specificityPatternBody('specificity-class') . '|blocks-engine-disclosure-summary-[a-f0-9]{6,}(?:-\d+)?)/', '', $markup) ?? $markup; - // A captured dialog's generated id, and its triggers' ids, carry a per-document hash. + // A captured dialog's generated id, and its triggers' ids, carry a per-document hash; + // the capture's own panel handle (id, panel attribute, aria-controls) carries a per-document ordinal. + $markup = preg_replace('/\b' . preg_quote(CaptureEvidence::DIALOG_CLASS, '/') . '-\d+\b/', CaptureEvidence::DIALOG_CLASS, $markup) ?? $markup; $markup = preg_replace('/blocks-engine-dialog-trigger-[a-f0-9]+-(\d+)/', 'blocks-engine-dialog-trigger-$1', $markup) ?? $markup; $markup = preg_replace('/blocks-engine-dialog-[a-f0-9]{16}\b/', 'blocks-engine-dialog', $markup) ?? $markup; $markup = preg_replace('/\s*be-inline-geometry-[a-f0-9]{16}(?:-[a-f0-9]{16})?/', '', $markup) ?? $markup; diff --git a/php-transformer/tests/unit/captured-dialog-ordinal-shared-header.php b/php-transformer/tests/unit/captured-dialog-ordinal-shared-header.php new file mode 100644 index 000000000..f96532266 --- /dev/null +++ b/php-transformer/tests/unit/captured-dialog-ordinal-shared-header.php @@ -0,0 +1,68 @@ +
  • Home
  • About
  • Contact
  • '; +$header = static fn(int $n, string $dialogLinks): string => '
    Acme' . "\n" + . '' . "\n" + . '
    '; + +$compile = static function (callable $dialogFor) use ($header): array { + $files = array(); + foreach (array('index.html' => 'Home', 'about.html' => 'About', 'contact.html' => 'Contact') as $path => $title) { + $n = count($files); + $files[] = array('path' => $path, 'content' => '' . $title . '' . $header($n, $dialogFor($n)) . '

    ' . $title . '

    ' . $title . ' copy.

    '); + } + return (new ArtifactCompiler())->compile(array('entrypoint' => 'index.html', 'files' => $files))->toWordPressSitePlanView()['wordpress_site_plan'] ?? array(); +}; +$parts = static fn(array $plan, string $area): array => array_values(array_filter($plan['template_parts'] ?? array(), static fn(array $part): bool => $area === ($part['area'] ?? null))); +$template = static function (array $plan, string $slug): string { + foreach ($plan['templates'] ?? array() as $template) { + if ($slug === ($template['slug'] ?? null)) return (string) ($template['content'] ?? $template['canonical_block_markup'] ?? ''); + } + return ''; +}; + +// Identical headers that differ only by the capture's dialog ordinal. +$plan = $compile(static fn(int $n): string => $links); +$diagnostic = (string) json_encode(array_map(static fn(array $part): array => array('area' => $part['area'] ?? null, 'slug' => $part['slug'] ?? null), $plan['template_parts'] ?? array())); +$assert(3 === count($plan['pages'] ?? array()), 'all three routes compile to pages', (string) json_encode(array_column($plan['pages'] ?? array(), 'source_path'))); +$assert(1 === count($parts($plan, 'header')), 'the header differing only by dialog ordinal becomes one template part', $diagnostic); +$assert(1 === count($parts($plan, 'footer')), 'the footer becomes one template part', $diagnostic); +foreach (array('page', 'front-page') as $slug) { + $markup = $template($plan, $slug); + if ('' === $markup) continue; + $assert(str_contains($markup, '"area":"header"') || str_contains($markup, '"slug":"header'), $slug . ' includes the header part', $markup); + $assert(str_contains($markup, '"area":"footer"') || str_contains($markup, '"slug":"footer'), $slug . ' includes the footer part', $markup); +} + +// Negative control: dialogs that differ in content keep the header unshared. +$control = $compile(static fn(int $n): string => ''); +$assert(0 === count($parts($control, 'header')), 'headers whose dialogs differ in content stay unshared', (string) json_encode(array_column($control['template_parts'] ?? array(), 'area'))); +$assert(1 === count($parts($control, 'footer')), 'the control footer is still shared', (string) json_encode(array_column($control['template_parts'] ?? array(), 'area'))); + +fwrite(STDOUT, "Captured dialog ordinal shared header: {$passes} passed, {$failures} failed\n"); +exit($failures ? 1 : 0);