diff --git a/.github/workflows/figma-transformer.yml b/.github/workflows/figma-transformer.yml new file mode 100644 index 00000000..2ff96655 --- /dev/null +++ b/.github/workflows/figma-transformer.yml @@ -0,0 +1,42 @@ +name: Figma Transformer + +on: + pull_request: + paths: + - ".github/workflows/figma-transformer.yml" + - "figma-transformer/**" + push: + branches: + - trunk + paths: + - ".github/workflows/figma-transformer.yml" + - "figma-transformer/**" + +jobs: + validate: + name: Composer validate and test + runs-on: ubuntu-latest + + defaults: + run: + working-directory: figma-transformer + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: "8.1" + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Validate Composer package metadata + run: composer validate --strict + + - name: Run package tests + run: composer test diff --git a/.github/workflows/php-transformer.yml b/.github/workflows/php-transformer.yml index 145b1c4a..6d6573be 100644 --- a/.github/workflows/php-transformer.yml +++ b/.github/workflows/php-transformer.yml @@ -5,12 +5,16 @@ on: paths: - ".github/workflows/php-transformer.yml" - "php-transformer/**" + - "scripts/production-acceptance-matrix.php" + - "tests/contract/production-acceptance-matrix.php" push: branches: - trunk paths: - ".github/workflows/php-transformer.yml" - "php-transformer/**" + - "scripts/production-acceptance-matrix.php" + - "tests/contract/production-acceptance-matrix.php" jobs: validate: @@ -41,6 +45,36 @@ jobs: - name: Run package tests run: composer test + - name: Run repository acceptance matrix contract + working-directory: . + run: php tests/contract/production-acceptance-matrix.php + + visual-parity: + name: Generic visual parity tools + runs-on: ubuntu-latest + + defaults: + run: + working-directory: php-transformer/tools/visual-parity + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + run: npm ci + + - name: Install Chromium + run: npx playwright install --with-deps chromium + + - name: Run visual parity tests + run: npm test + wordpress-site-plan: name: WordPress site plan integration runs-on: ubuntu-24.04 diff --git a/figma-transformer/scripts/figma-fixture-matrix.php b/figma-transformer/scripts/figma-fixture-matrix.php index 7908ac5e..007fbcca 100755 --- a/figma-transformer/scripts/figma-fixture-matrix.php +++ b/figma-transformer/scripts/figma-fixture-matrix.php @@ -950,7 +950,9 @@ function matrix_dom_box_capture_command(string $homeboyCommand, string $domBoxPr return $command; } - $environment = 'HOMEBOY_DOM_BOX_CAPTURE_COMMAND=' . escapeshellarg($domBoxProviderCommand); + $environment = 'HOMEBOY_DOM_BOX_CAPTURE_COMMAND=' . escapeshellarg($domBoxProviderCommand) + . ' HOMEBOY_DOM_BOX_NODE_ID_ATTR=' . escapeshellarg('data-figma-node-id') + . ' HOMEBOY_DOM_BOX_NODE_NAME_ATTR=' . escapeshellarg('data-figma-node-name,data-figma-name'); if ( ! empty($captureTargets) ) { $environment .= ' HOMEBOY_DOM_BOX_CAPTURE_TARGETS_JSON=' . escapeshellarg((string) json_encode($captureTargets, JSON_UNESCAPED_SLASHES)); } diff --git a/figma-transformer/tests/contract/FixtureMatrixContract.php b/figma-transformer/tests/contract/FixtureMatrixContract.php index ac05e8ea..eb71aafd 100644 --- a/figma-transformer/tests/contract/FixtureMatrixContract.php +++ b/figma-transformer/tests/contract/FixtureMatrixContract.php @@ -862,6 +862,8 @@ function blocks_engine_figma_transformer_run_fixture_matrix_contract(callable $a $matrixAliasCaptureCommand = (string) ($matrixAliasSummary['fixtures'][0]['dom_box_capture']['command'] ?? ''); $assert(str_contains($matrixAliasCaptureCommand, escapeshellarg('/opt/homeboy-alias')), 'fixture-matrix-alias-capture-uses-homeboy-bin'); $assert(str_contains($matrixAliasCaptureCommand, 'HOMEBOY_DOM_BOX_CAPTURE_COMMAND=' . escapeshellarg('node dom-box-alias')), 'fixture-matrix-alias-capture-uses-dom-box-command'); + $assert(str_contains($matrixAliasCaptureCommand, 'HOMEBOY_DOM_BOX_NODE_ID_ATTR=' . escapeshellarg('data-figma-node-id')), 'fixture-matrix-supplies-figma-node-id-contract'); + $assert(str_contains($matrixAliasCaptureCommand, 'HOMEBOY_DOM_BOX_NODE_NAME_ATTR=' . escapeshellarg('data-figma-node-name,data-figma-name')), 'fixture-matrix-supplies-figma-node-name-contract'); $assert(is_array($matrixCanonicalSummary), 'fixture-matrix-canonical-json-summary'); $assert('/opt/homeboy-canonical' === ($matrixCanonicalSummary['homeboy_command'] ?? null), 'fixture-matrix-homeboy-command-canonical'); diff --git a/figma-transformer/tests/contract/KiwiParserContract.php b/figma-transformer/tests/contract/KiwiParserContract.php index b9e54217..fd114c06 100644 --- a/figma-transformer/tests/contract/KiwiParserContract.php +++ b/figma-transformer/tests/contract/KiwiParserContract.php @@ -80,7 +80,7 @@ function blocks_engine_figma_transformer_run_kiwi_parser_contract(callable $asse . pack('V', 106) . blocks_engine_figma_transformer_kiwi_chunk("\x28\xb5\x2f\xfd" . 'adapter-frame') ); - $failingAdapterResult = ( new ZstdCapability(static fn (): false => false) )->uncompress("\x28\xb5\x2f\xfd" . 'adapter-frame', 'ContractTest', 3); + $failingAdapterResult = ( new ZstdCapability(static fn (): bool => false) )->uncompress("\x28\xb5\x2f\xfd" . 'adapter-frame', 'ContractTest', 3); $commandAdapter = new ZstdCapability(new ZstdCommandDecoder(array(PHP_BINARY, '-r', '$payload = stream_get_contents(STDIN); fwrite(STDOUT, $payload);'))); $commandAdapterResult = $commandAdapter->uncompress('command adapter bytes', 'ContractTest', 4); $limitedCommandAdapterResult = $commandAdapter->uncompress('command adapter bytes', 'ContractTest', 5, array('max_decoded_bytes' => 1)); diff --git a/php-transformer/composer.json b/php-transformer/composer.json index 245fdb28..85396c08 100644 --- a/php-transformer/composer.json +++ b/php-transformer/composer.json @@ -52,7 +52,6 @@ "php tests/contract/run.php", "php tests/contract/finding-contract.php", "php tests/contract/wordpress-site-plan.php", - "php tests/contract/production-acceptance-matrix.php", "@test:unit" ], "test:unit": [ diff --git a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php index 04aa91bc..8cdadebc 100644 --- a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php +++ b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php @@ -5894,7 +5894,7 @@ private function searchBlockFromForm(DOMElement $form): ?array */ private function searchBlockFromStandaloneControl(DOMElement $element): ?array { - if ( 0 < $element->getElementsByTagName('form')->length || 0 < $element->getElementsByTagName('script')->length || array() !== $this->eventMetadata($element) ) { + if ( 0 < $element->getElementsByTagName('form')->length || 0 < $element->getElementsByTagName('script')->length || array() !== $this->eventMetadata($element) || $this->isRuntimeDomTarget($element) ) { return null; } @@ -5904,10 +5904,9 @@ private function searchBlockFromStandaloneControl(DOMElement $element): ?array $inputs[] = $input; } } - if ( 1 !== count($inputs) || array() !== $this->eventMetadata($inputs[0]) ) { + if ( 1 !== count($inputs) || array() !== $this->eventMetadata($inputs[0]) || $this->isRuntimeDomTarget($inputs[0]) ) { return null; } - $controls = $this->formControlElements($element); if ( 1 !== count($controls) ) { return null; @@ -5925,7 +5924,21 @@ private function searchBlockFromStandaloneControl(DOMElement $element): ?array if ( '' === $label ) { $label = $this->attr($searchInput, 'placeholder'); } - return $this->htmlPreservationBlock($element); + + if ( '' !== $this->attr($searchInput, 'id') || 's' !== $this->attr($searchInput, 'name') ) { + return $this->htmlPreservationBlock($element); + } + if ( 1 !== $this->childElementCount($element) ) { + return null; + } + + $placeholder = $this->attr($searchInput, 'placeholder'); + return $this->createBlock('core/search', array_merge($this->presentationAttributes($element), array( + 'label' => '' !== $label ? $label : 'Search', + 'showLabel' => false, + 'placeholder' => $placeholder, + 'buttonPosition' => 'no-button', + )), array(), $element); } /** diff --git a/php-transformer/tests/contract/run.php b/php-transformer/tests/contract/run.php index a8ffeac7..9350dff2 100644 --- a/php-transformer/tests/contract/run.php +++ b/php-transformer/tests/contract/run.php @@ -755,6 +755,23 @@ public function match(DOMElement $element, PatternContext $context): ?array $assert('select' === ($standaloneControls['source_reports']['runtime_islands'][0]['control']['tag'] ?? ''), 'runtime-targeted standalone control reports control metadata'); $assert(str_contains((string) ($standaloneControls['source_reports']['runtime_islands'][0]['source_snippet'] ?? ''), '' +)->toArray(); +$standaloneSearchBlock = $standaloneSearch['blocks'][0] ?? array(); +$assert('core/search' === ($standaloneSearchBlock['blockName'] ?? ''), 'script-free standalone search input converts to core/search'); +$assert('Search articles' === ($standaloneSearchBlock['attrs']['placeholder'] ?? ''), 'standalone core/search preserves the source placeholder'); +$assert('no-button' === ($standaloneSearchBlock['attrs']['buttonPosition'] ?? ''), 'standalone input-only search keeps the no-button presentation'); +$assert(! str_contains((string) ($standaloneSearch['serialized_blocks'] ?? ''), '