diff --git a/php-transformer/composer.json b/php-transformer/composer.json index f78eff420..1ccfaa224 100644 --- a/php-transformer/composer.json +++ b/php-transformer/composer.json @@ -194,6 +194,7 @@ "php tests/unit/site-marker-document-scan.php", "php tests/unit/form-offscreen-submit-presenter.php", "php tests/unit/form-submit-caption-box.php", + "php tests/unit/form-label-carrier-case.php", "php tests/unit/runtime-declarations-streaming-hash.php", "php tests/unit/external-metric-declarations.php", "php tests/unit/runtime-script-evidence-analyzer.php", diff --git a/php-transformer/src/HtmlToBlocks/Style/FormPresentationGraphBuilder.php b/php-transformer/src/HtmlToBlocks/Style/FormPresentationGraphBuilder.php index de3a67e01..5cb66678f 100644 --- a/php-transformer/src/HtmlToBlocks/Style/FormPresentationGraphBuilder.php +++ b/php-transformer/src/HtmlToBlocks/Style/FormPresentationGraphBuilder.php @@ -32,12 +32,11 @@ final class FormPresentationGraphBuilder /** Authored values such as system font stacks exceed a few hundred bytes; bound like selectors and conditions. */ private const MAX_STYLE_VALUE_BYTES = 1024; private const MAX_DIAGNOSTICS = 32; - /** The text properties a label role reads from its text carrier when the label declares none itself. */ + /** + * The inherited text properties a label or button caption reads from its + * text carrier when it declares none itself, including the caption's case. + */ private const TYPOGRAPHY_PROPERTIES = array( - 'color', 'font', 'font-family', 'font-size', 'font-weight', 'letter-spacing', 'line-height', - ); - /** A button caption carrier also sets the caption's case. */ - private const CAPTION_PROPERTIES = array( 'color', 'font', 'font-family', 'font-size', 'font-weight', 'letter-spacing', 'line-height', 'text-transform', ); /** A block-level caption carrier fills its button, so its padding sizes the button box. */ @@ -743,7 +742,7 @@ private static function assertRole(mixed $role, ?array $condition, bool $allowEm */ private function carrierTypography(array $roles, DOMElement $label, array $rules, array $customPropertyRules, array $labelConditional = array(), bool $captionBox = false): array { - $properties = array_flip(array() === $roles ? self::CAPTION_PROPERTIES : self::TYPOGRAPHY_PROPERTIES); + $properties = array_flip(self::TYPOGRAPHY_PROPERTIES); $excluded = array_values(array_filter( array( $roles['control'] ?? null, $roles['required_marker'] ?? null ), static fn (mixed $element): bool => $element instanceof DOMElement diff --git a/php-transformer/tests/unit/form-label-carrier-case.php b/php-transformer/tests/unit/form-label-carrier-case.php new file mode 100644 index 000000000..391e1fa58 --- /dev/null +++ b/php-transformer/tests/unit/form-label-carrier-case.php @@ -0,0 +1,57 @@ + + * Name` — takes that text's + * case from the carrier, the same way it takes the carrier's font and color. + * `text-transform` is an inherited text property, so a provider label that + * renders the caption directly must keep it, and an in-form heading that + * inherits it from an ancestor keeps it too. + */ + +require dirname(__DIR__, 2) . '/vendor/autoload.php'; + +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Style\FormPresentationGraphBuilder; + +$failures = 0; +$passes = 0; +$assert = static function (bool $ok, string $message, string $detail = '') use (&$failures, &$passes): void { + if ( $ok ) { + ++$passes; + return; + } + ++$failures; + fwrite(STDERR, 'FAIL: ' . $message . ( '' !== $detail ? ' - ' . $detail : '' ) . PHP_EOL); +}; + +$document = static function (string $body): DOMDocument { + $document = new DOMDocument(); + $document->loadHTML('
' . $body . '', LIBXML_NOERROR | LIBXML_NOWARNING); + return $document; +}; +$label = static function (string $css) use ($document): array { + $form = $document('')->getElementsByTagName('form')->item(0); + $graph = (new FormPresentationGraphBuilder())->build($form, array(), $css); + return array( 'styles' => array_column($graph['controls'], null, 'index')[0]['label']['styles'] ?? array(), 'variants' => $graph['variants'] ?? array() ); +}; + +$carrier = $label('.field { display: flex; flex-direction: column; } .field__label { font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; }'); +$assert('uppercase' === ($carrier['styles']['text_transform'] ?? null) && '.14em' === ($carrier['styles']['letter_spacing'] ?? null), 'the label text carrier sets the caption case alongside its typography', json_encode($carrier['styles'])); + +$own = $label('.field { text-transform: lowercase; } .field__label { text-transform: uppercase; }'); +$assert('uppercase' === ($own['styles']['text_transform'] ?? null), 'the carrier case wins over the label case for the text it paints', json_encode($own['styles'])); + +$responsive = $label('.field__label { text-transform: none; } @media (min-width: 600px) { .field__label { text-transform: uppercase; } }'); +$patches = array_map(static fn (array $variant): array => $variant['style_patch'] ?? array(), array_filter($responsive['variants'], static fn (array $variant): bool => 0 === ($variant['index'] ?? null) && 'label' === ($variant['role'] ?? null))); +$assert('none' === ($responsive['styles']['text_transform'] ?? null) && in_array('uppercase', array_column($patches, 'text_transform'), true), 'a conditional carrier case becomes a label variant', json_encode(array( $responsive['styles'], $patches ))); + +$heading = $document('')->getElementsByTagName('p')->item(0); +$inherited = (new FormPresentationGraphBuilder())->buildElement($heading, array(), '.eyebrow { text-transform: uppercase; }'); +$assert('uppercase' === ($inherited['styles']['text_transform'] ?? null), 'in-form copy inherits its case from the nearest declaring ancestor', json_encode($inherited)); + +if ( 0 < $failures ) { + fwrite(STDERR, "form label carrier case FAILED: {$passes} passed, {$failures} failed\n"); + exit(1); +} +echo "form label carrier case passed: {$passes} assertions\n";