Commit 84ae3531d23 for woocommerce
commit 84ae3531d234e732a5ff0d9c7eabe7a90624dbbf
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Mon Sep 7 17:11:08 2026 +0300
[tests] Refresh generated block.json copies deterministically for PHP tests (#68239)
* fix(blocks): refresh PHP test metadata deterministically
PHP test suites consume generated copies of each block manifest.
The previous script copied only missing files and parsed block names with a
regex that also matched nested style variations. Existing targets could stay
stale and local test behavior could diverge from clean CI.
Validate the source manifest set, refresh its exact generated counterparts,
and remove stale metadata without disturbing non-metadata assets.
Refs TESTOPS-234
* refactor(blocks): simplify the block.json copy script
The rewrite in the previous commit carried a hand-rolled recursive
find, a whole-tree empty-directory walk, a redundant existence guard,
and two throws for one condition. None of that earned its place.
Use readdirSync's recursive mode, prune only the directories each
removed manifest leaves behind, and explain why the metadata collection
is dropped: a stale blocks-json.php would override the fresh block.json
files at registration time until the next full build. Point the
webpack-entries sync comment at the renamed set.
Refs TESTOPS-234
* fix(blocks): validate the whole block name, not just its presence
The copy script derived the target directory from the second `/`
segment of `metadata.name` and only checked that the segment was
non-empty. That check let two malformed names through. A name of
`woocommerce/foo/bar` silently dropped `bar` and copied the manifest
to `foo/`. A name of `woocommerce/..` passed the non-empty check and
resolved, via `path.join`, to a manifest written one level above the
target directory — where the cleanup scan, which only walks the target
directory, could never remove it again.
Match the name against the `namespace/block-name` pattern WordPress
itself enforces and take the block name from the capture group. All
174 source manifests already conform, so the copy output is unchanged;
what changes is that "Invalid block name" now means the name is
actually invalid, rather than merely absent.
Refs TESTOPS-234
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(blocks): reject non-string block names before matching
The name guard coerced `metadata.name` with `String( metadata.name ?? '' )`
before matching it against the `namespace/block-name` pattern. Coercion
defeats the check for array values: `String( [ 'woocommerce/foo' ] )` is
`'woocommerce/foo'`, so a manifest whose `name` is a single-element array
passed validation and was copied verbatim, array-valued `name` and all.
Two nearby contracts already reject that shape, which made this script the
outlier. `bin/block.json-validation-schema.json` pairs the exact same
pattern with `"type": "string"`, and the CopyWebpackPlugin rule this script
mirrors calls `metadata.name.split()`, which throws on a non-string.
Check `typeof metadata.name === 'string'` and match the raw value instead.
The `?? ''` fallback goes away with the coercion and is not replaced: null
and undefined now fail the type check, where before they fell through to an
empty string that failed the pattern. All 174 source manifests are
unaffected, so the copy output is unchanged.
Refs TESTOPS-234
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: add the plugin changelog entry for the manifest refresh
The branch carried only a `client/blocks/changelog/` entry. Changes under
`plugins/woocommerce/client/blocks/` also need an entry in the plugin's own
`plugins/woocommerce/changelog/`, which is the one that feeds the release
notes; every recent commit touching that tree adds one.
Mark it `Type: dev` with a `Comment:` and no entry body, matching the
sibling TESTOPS-234 entry: the change is confined to test tooling and has
no user-facing effect to report.
Refs TESTOPS-234
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/testops-234-refresh-block-manifest-copies b/plugins/woocommerce/changelog/testops-234-refresh-block-manifest-copies
new file mode 100644
index 00000000000..4fc5da81787
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-234-refresh-block-manifest-copies
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+Comment: Refresh generated block.json copies deterministically so PHP test metadata matches the source manifests.
+
diff --git a/plugins/woocommerce/client/blocks/bin/copy-blocks-json.sh b/plugins/woocommerce/client/blocks/bin/copy-blocks-json.sh
index 6adfca91c56..96c8acb4b66 100755
--- a/plugins/woocommerce/client/blocks/bin/copy-blocks-json.sh
+++ b/plugins/woocommerce/client/blocks/bin/copy-blocks-json.sh
@@ -1,9 +1,10 @@
#!/bin/bash
-# This script mimics the CopyWebpackPlugin behavior from the WooCommerce Blocks webpack configuration
-# to make block.json files available for unit testing without requiring a full build.
-# Ensure that the logic of this script is kept in sync with the logic of the CopyWebpackPlugin in the WooCommerce Blocks webpack configuration:
+# Copies every block.json under the Blocks source tree into the built assets directory so the PHP
+# test suites can read block metadata without a full build. Mirrors the CopyWebpackPlugin rules in
# https://github.com/woocommerce/woocommerce/blob/84d1da7be3cbd3d8f40b17ad58729f668fd82b6a/plugins/woocommerce/client/blocks/bin/webpack-configs.js#L229-L256
+# and must stay in sync with them. Unlike a plain copy, it first removes the previously generated
+# manifests so an edited or deleted source manifest cannot leave a stale copy behind.
# Move to the project root
while [ ! -d "plugins/woocommerce" ] || [ ! -f "pnpm-workspace.yaml" ]; do
@@ -14,43 +15,68 @@ while [ ! -d "plugins/woocommerce" ] || [ ! -f "pnpm-workspace.yaml" ]; do
cd ..
done
-# Set target directory
-TARGET_DIR="plugins/woocommerce/assets/client/blocks"
-
-# Create target directory if it doesn't exist
-mkdir -p "$TARGET_DIR"
-
-# Define generic blocks as a space-separated string (keep in sync with webpack-entries.js)
-generic_blocks="accordion-group accordion-header accordion-item accordion-panel"
-
-# Find all block.json files
-find plugins/woocommerce/client/blocks/assets/js -name "block.json" | while read file; do
- # Read the block name from the JSON file
- block_name=$(cat "$file" | grep -o '"name": "[^"]*"' | cut -d'"' -f4 | cut -d'/' -f2)
-
- # Function to check if a block is in the generic_blocks string
- is_generic_block=false
- for gb in $generic_blocks; do
- if [ "$block_name" = "$gb" ]; then
- is_generic_block=true
- break
- fi
- done
-
- # Check if it's a parent block by looking for "parent" field, but treat as regular if generic
- if grep -q '"parent":' "$file" && [ "$is_generic_block" = false ]; then
- # It's an inner block
- target_path="$TARGET_DIR/inner-blocks/$block_name/block.json"
- mkdir -p "$TARGET_DIR/inner-blocks/$block_name"
- if [ ! -f "$target_path" ]; then
- cp "$file" "$target_path"
- fi
- else
- # It's a regular block
- target_path="$TARGET_DIR/$block_name/block.json"
- mkdir -p "$TARGET_DIR/$block_name"
- if [ ! -f "$target_path" ]; then
- cp "$file" "$target_path"
- fi
- fi
-done
+node <<'NODE'
+const fs = require( 'node:fs' );
+const path = require( 'node:path' );
+
+const sourceDirectory = path.resolve( 'plugins/woocommerce/client/blocks/assets/js' );
+const targetDirectory = path.resolve( 'plugins/woocommerce/assets/client/blocks' );
+// Keep in sync with genericBlocks in webpack-entries.js.
+const genericBlocks = new Set( [
+ 'accordion-group',
+ 'accordion-header',
+ 'accordion-item',
+ 'accordion-panel',
+] );
+
+const findManifests = ( directory ) =>
+ fs
+ .readdirSync( directory, { recursive: true } )
+ .filter( ( entry ) => path.basename( entry ) === 'block.json' )
+ .map( ( entry ) => path.join( directory, entry ) );
+
+const sourceManifests = findManifests( sourceDirectory );
+if ( sourceManifests.length === 0 ) {
+ // Refuse to wipe the target when the source scan found nothing: that is a broken checkout, not an empty one.
+ throw new Error( `No block metadata manifests found in ${ sourceDirectory }` );
+}
+
+fs.mkdirSync( targetDirectory, { recursive: true } );
+
+for ( const targetManifest of findManifests( targetDirectory ) ) {
+ fs.unlinkSync( targetManifest );
+ // Prune the directories this copy created once they are empty, so a renamed or removed block leaves nothing behind.
+ let directory = path.dirname( targetManifest );
+ while ( directory !== targetDirectory && fs.readdirSync( directory ).length === 0 ) {
+ fs.rmdirSync( directory );
+ directory = path.dirname( directory );
+ }
+}
+
+// The full build writes a metadata collection that overrides the individual block.json files at
+// registration time; a stale one would mask the fresh copies until the next build, so drop it too.
+fs.rmSync( path.join( targetDirectory, 'blocks-json.php' ), { force: true } );
+
+for ( const sourceManifest of sourceManifests ) {
+ const metadata = JSON.parse( fs.readFileSync( sourceManifest, 'utf8' ) );
+ // Block names are `namespace/block-name`, the shape WordPress and bin/block.json-validation-schema.json
+ // both enforce. Reject anything else: a name like `woocommerce/..` would otherwise resolve to a path
+ // outside the target directory.
+ const blockName =
+ typeof metadata.name === 'string'
+ ? /^[a-z][a-z0-9-]*\/([a-z][a-z0-9-]*)$/.exec( metadata.name )?.[ 1 ]
+ : undefined;
+ if ( ! blockName ) {
+ throw new Error( `Invalid block name in ${ sourceManifest }` );
+ }
+
+ const targetManifest = path.join(
+ targetDirectory,
+ metadata.parent && ! genericBlocks.has( blockName ) ? path.join( 'inner-blocks', blockName ) : blockName,
+ 'block.json'
+ );
+
+ fs.mkdirSync( path.dirname( targetManifest ), { recursive: true } );
+ fs.copyFileSync( sourceManifest, targetManifest );
+}
+NODE
diff --git a/plugins/woocommerce/client/blocks/bin/webpack-entries.js b/plugins/woocommerce/client/blocks/bin/webpack-entries.js
index ab51a5bd5bf..78b4cd48f11 100644
--- a/plugins/woocommerce/client/blocks/bin/webpack-entries.js
+++ b/plugins/woocommerce/client/blocks/bin/webpack-entries.js
@@ -238,7 +238,7 @@ const blocks = {
/**
* Blocks that are generic and will likely be pushed up to Gutenberg or a public block registry.
- * Keep in sync with the generic_blocks array in copy-blocks-json.sh
+ * Keep in sync with genericBlocks in copy-blocks-json.sh
*/
const genericBlocks = {
'accordion-group': {
diff --git a/plugins/woocommerce/client/blocks/changelog/testops-234-refresh-block-manifest-copies b/plugins/woocommerce/client/blocks/changelog/testops-234-refresh-block-manifest-copies
new file mode 100644
index 00000000000..30a55d87fc4
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/changelog/testops-234-refresh-block-manifest-copies
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Refresh generated block.json copies exactly and prune stale ones so PHP test metadata matches the source manifests.