Skip to content

[tests] Demote 4 Products block E2E titles to Jest and PHPUnit - #68667

Open
vladolaru wants to merge 2 commits into
trunkfrom
testops-234/products-block
Open

[tests] Demote 4 Products block E2E titles to Jest and PHPUnit#68667
vladolaru wants to merge 2 commits into
trunkfrom
testops-234/products-block

Conversation

@vladolaru

@vladolaru vladolaru commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Submission Review Guidelines:

Changes proposed in this Pull Request:

The Products block — the deprecated core/query variation registered under the woocommerce/product-query namespace — spent seven Playwright identities on contracts that do not need a browser. This moves those contracts down a layer and keeps three browser journeys.

Refs TESTOPS-234

Part of the #68046 split.

Six files, 212 insertions and 73 deletions. No production code changes.

Layer Before After
products.block_theme.spec.ts runtime titles 7 3
ProductQuery.php PHPUnit methods 16 17
product-query/test/inspector-controls.tsx Jest cases 0 (file is new) 3

Please merge #68618 before this one. The reason is in the next section, and it is about which pull request carries a test rather than about risk to coverage.

Read this before the table: one absorber ships in a sibling pull request

Three of the four removed titles are archive-route parity checks, and their named replacement is RouteContextParityTest::test_product_collection_and_product_query_match_classic_route. That method's Products arm was written by this batch's slice commit, but it lands inside a file that #68618 carries whole, so it travels with that pull request. It is not on trunk and it is not in this diff — git cat-file -e origin/trunk:plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/RouteContextParityTest.php fails today. Anyone grepping this diff for that class will not find it, which is why it is stated here rather than left in a table cell.

This is a citation-integrity problem, not a coverage cliff, and the difference matters if #68618 stalls:

  • product-collection.block_theme.spec.ts on trunk already runs a parity loop over Product Category (/product-category/music/), Product Tag (/product-tag/recommended/), Product Catalog (/shop/) and Product Search Results (/?s=shirt&post_type=product), asserting toHaveCount( expectedProductsCount ) and then name equality against the classic loop. That is a cardinality floor the removed Products titles never had — they compared two scraped arrays with toEqual, which passes on two empty arrays.
  • So those three routes keep a real browser owner either way. What only [tests] Reduce Product Collection frontend E2E tests from 37 to 20 #68618 adds is an owner that renders the Products block specifically, rather than Product Collection.

If #68618 stalls, this pull request is still mergeable provided the table below is amended to name trunk's Product Collection parity titles as the interim route owner.

Removed and rewritten titles

Removed E2E test Existing coverage Knowingly dropped
core/query Block › product button should add product to the cart when not inheriting query from template tests/php/src/Blocks/BlockTypes/ProductQuery.php::test_add_iapi_context_updates_only_valid_product_loop_items (in this PR) owns the per-item Interactivity wiring the button depends on: the exact woocommerce/products namespace, the product-item-{id} key and the decoded { productId, variationId: null } context, proven on real products and proven absent on three kinds of invalid loop item Browser proof that a Products block with inherit: false on a published post hydrates and completes a real add-to-cart click. The PHP owner asserts the context attributes that drive the button, not the click, the 1 in cart label or the View cart link. The retained inheriting-query title keeps that journey character-for-character, but on the archive route only, so the second query mode has no end-to-end owner
Product Category template › Products block matches with classic template block RouteContextParityTest::…match_classic_route, provider row product category, expected [ 'Parity Shirt B', 'Parity Shirt A' ]ships in #68618, not here. Interim browser owner on trunk: product-collection.block_theme.spec.tsProduct Category templateProduct Collection block matches with classic template block Browser proof that the category template itself resolves and renders through a Site Editor save. The PHP owner renders through do_blocks against an in-process query, so template authoring and URL routing are not covered by it
Product Tag template › Products block matches with classic template block Same method, provider row product tag, expected [ 'Parity Shirt C', 'Parity Shirt A' ]ships in #68618. Same interim browser owner on trunk As above. See the note below: this title was not testing what its name said
Product Search Results template › Products block matches with classic template block Same method, provider row product search, expected [ 'Parity Shirt B', 'Parity Shirt C', 'Parity Shirt A' ]ships in #68618. Same interim browser owner on trunk As above
Product Catalog template › Products block matches with classic template block Not removed — renamed and strengthened into Product Collection matches with classic template block, which stays in the browser Browser proof that the un-migrated Products block renders the catalog. After the upgrade click the title compares Product Collection instead. The block still renders on /shop/ in the retained add-to-cart title, so its catalog render keeps a browser owner, but its ordered parity with the classic loop on that route now has no owner at any layer: RouteContextParityTest's provider has category, tag and search rows and no archive-product row

The removed Product Tag title never tested a Product Tag template. The base loop hard-codes templateName: 'Products by Category' inside the shared body, so both needsCreation rows created a category template; the Tag iteration then visited /product-tag/recommended/ and compared two arrays that were empty. Worth knowing before treating its removal as a loss.

Relocated to Jest

useAllowedControls decides which inspector controls the block offers. Three cases: the inherited Site Editor branch returns only wooInherit; re-rendering with inherit: false restores the full list, which proves the hook reacts to the attribute rather than caching its first branch; the Post Editor branch removes wooInherit and keeps everything else.

The retained wiring test

Three titles survive, all in this batch's own spec, and --list shows exactly these three:

  1. core/query Block › when Inherits Query From Template other options are hidden, show up otherwise — inserts the block in the Site Editor and reads the real inspector.
  2. core/query Block › product button should add product to the cart when inheriting query from template — the only surviving title anywhere that renders this block on a front end and buys from it. It carries the block's entire frontend contract.
  3. Product Catalog template › Product Collection matches with classic template block — the upgrade path, asserting the migrated query's isProductCollectionBlock, inherit and perPage before the save and again after a reload, with cardinality floors on both sides.

One more lives outside this PR: product-collection.block_theme.spec.tsCan be migrated to from Products (Deprecated) block is, after this change, the block's only Post Editor coverage.

Things a reviewer should not have to find

Everything in this list is left exactly as the migration branch wrote it, because this pull request moves tests rather than improving them. They are listed so nobody has to rediscover them.

  • The rewritten title's mutation observer is a Product Collection symbol, ProductCollection/Utils.php::prepare_and_execute_query, not a Products one. That follows from the title now clicking through the upgrade: once migrated, the frontend query belongs to Product Collection.
  • insertProductsQuery's inherit option is now dead. Its only { inherit: false } caller was the removed title. The parameter is kept as the migration branch has it.
  • There is a race window at products.block_theme.spec.ts:138-140. The post-reload read waits on the canvas body, which can resolve before the editor store has parsed, and the ?? {} fallback in utils.ts:29 turns that into a hard failure rather than a retry. It passed three times here at --retries=0 --workers=1, and a fourth run cleared the same window before failing where its mutation aimed. The alternative is adding a wait, which this campaign does not do.
  • The Jest suite's @wordpress/data mock ignores the store it is asked for. The fake select returns the same variation whatever key it receives, so select( WP_BLOCKS_STORE ).getActiveBlockVariation( QUERY_LOOP_ID, attributes ) would behave identically with the wrong store, without QUERY_LOOP_ID, or without forwarding attributes — mutations to any of those three would survive this suite. What the three cases do assert is useAllowedControls' branching, and all three are mutation-killed on exactly that.
  • test_add_iapi_context_updates_only_valid_product_loop_items is @runInSeparateProcess with @preserveGlobalState disabled, which is unusual in tests/php. It is correct here: ProductsStore holds a static product cache and the method writes wp_interactivity_state(), and the test-case transaction rollback resets neither.
  • That method also carries an unreachable throw after an assertIsString. PHPUnit has already failed the test by the time the throw could run, so it is dead defensive code rather than a guard.

Screenshots or screen recordings:

Not applicable. Test-only change.

How to test the changes in this Pull Request:

  1. Check out this branch and run pnpm install --frozen-lockfile from the repository root.
  2. Run the Jest suite. No environment is needed — it is jsdom only.
    pnpm --filter=@woocommerce/block-library test:js --runInBand assets/js/blocks/product-query/test/inspector-controls.tsx
    Expect Tests: 3 passed, 3 total.
  3. Start the PHPUnit environment: pnpm --filter=@woocommerce/plugin-woocommerce env:test.
  4. Run the new PHP method: pnpm --filter=@woocommerce/plugin-woocommerce test:php:env -- --testsuite=wc-phpunit-main --filter 'test_add_iapi_context_updates_only_valid_product_loop_items'. Expect OK (1 test, 20 assertions).
  5. Confirm the PHP test detects a real regression. In plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php, in add_iapi_context(), change 'variationId' => null, to 'variationId' => 0,. Re-run step 4 and expect a failure on the decoded context array. Revert.
  6. Confirm it rejects non-products. In the same method, change if ( ! $product_id || 'product' !== get_post_type( $product_id ) ) { to if ( ! $product_id ) {. Re-run step 4 and expect Failed asserting that 'woocommerce/products' is null — the non-product list item now gets annotated. Revert.
  7. Confirm the Jest cases are real. In plugins/woocommerce/client/blocks/assets/js/blocks/product-query/utils.tsx, replace the Site Editor ternary's true branch controls.filter( ( control ) => control === 'wooInherit' ) with controls. Re-run step 2 and expect the inherited-query case to fail with expected [wooInherit], received [wooInherit, onSale]. Revert. No rebuild is needed; Jest transpiles the source directly.
  8. Start the Blocks E2E environment: pnpm --filter=@woocommerce/plugin-woocommerce env:e2e, then pnpm --filter=@woocommerce/plugin-woocommerce env:start:blocks. The second command is required, not optional — it seeds the store (products, options and editor preferences) that the specs assume. The admin storage state, the database snapshot and the block theme activation all come from the Playwright blocks setup project, which step 9 runs because it passes no --no-deps.
  9. Run the three retained titles: pnpm --filter=@woocommerce/plugin-woocommerce test:e2e:with-env default --project=blocks-chromium --retries=0 --workers=1 tests/e2e/tests/blocks/products/products.block_theme.spec.ts. Expect 4 passed, which is the three titles plus the setup project.
  10. Confirm the rewritten parity title has teeth. In plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/Utils.php, immediately after $query = clone $wp_query;, add $query->posts = array_slice( $query->posts, 0, 1 ); and $query->post_count = 1;. Re-run step 9 and expect a failure at expect( productCollectionProducts.length ).toBeGreaterThan( 1 ) — and note that the classic-side floor on the line above passes first, which is what proves only the collection's cloned query was truncated. Revert.

Testing that has already taken place:

Everything below ran on a local WordPress with retries disabled and one worker. trunk was at 7cda01098f.

  • Extraction. All four test files are byte-identical to the migration branch's copies at 8fe7a01548, verified by blob hash. Trunk drift on all four paths is empty since the diff base, so the change applies to trunk unmodified and nothing had to be re-derived.
  • Focused lower-layer runs. All five distinct focused commands from the mutation matrix exit 0. PHPUnit reports OK (1 test, 20 assertions) and each Jest command reports 2 skipped, 1 passed, 3 total — both the exact green identities the matrix recorded. The PHPUnit filter was checked to have executed a real test rather than matching nothing, which also exits 0.
  • Retained Playwright titles. --list shows exactly the three titles above; the run passes 4 passed at --retries=0 --workers=1.
  • Mutation re-check: all ten matrix rows, not the three the campaign's floor requires. The six PHPUnit rows all target the single new method but each disables a different load-bearing value — the namespace guard, the interactive namespace, the data-wp-key prefix, productId, variationId, and the post-type guard — so one kill would have spoken for one contract out of six. Each red fails at the assertion the matrix names and goes green on revert. The assertion counts corroborate the line numbers rather than just accompanying them: the clean run makes 20 assertions and the reds stop at 2, 2, 3, 6, 6 and 18, precisely where each mutated value is first read.
  • Every red was checked to be a genuine assertion failure, not a parse error, bootstrap failure, module-resolution error, login failure or missing-fixture error — all of which also exit non-zero. This mattered: an earlier round of the PHPUnit mutations produced three reds carrying ParseError and two greens that failed after revert, none of them real. The cause was the container's view of the bind-mounted source lagging the host's, so a run could execute a half-written or still-mutated file. Those runs were discarded and re-run behind a check that confirms the container hashes the same bytes as the host before either the red or the green is trusted.
  • Lint. oxlint exits 0 on the three changed JS/TS files. lint:changes:branch exits 0. phpcs run directly on the changed PHP test file reports two errors, and running phpcs on trunk's copy at the same path reports the identical pair — a missing strict_types declaration and a spacing error on a pre-existing closure that this change's insertion shifts from line 258 to line 344. Neither is introduced here.
  • PHPStan: not applicable, and checked rather than assumed. phpstan.neon scopes to woocommerce.php, src/ and includes/; no file in this diff is in scope.
  • Three independent agent reviews. The first two returned no blocking findings; a third, run over the corrections and this description, found two and both are fixed. Every finding was applied or declined in writing with a reason. Four corrections they produced are visible above: trunk's Product Collection parity loop is named as the interim route owner, the mis-wired Product Tag title is disclosed, the coverage table's expected fixture names were corrected to the ones [tests] Reduce Product Collection frontend E2E tests from 37 to 20 #68618 actually ships (they had been quoted from the migration branch, which renamed them), and step 8 no longer credits env:start:blocks with creating the storage state and snapshot that the blocks setup project creates. A stale mutation summary that contradicted its own logs was also regenerated from them.

Milestone

Note: Check the box above to have the milestone automatically assigned when merged.
Alternatively (e.g. for point releases), manually assign the appropriate milestone.

Changelog entry

  • Automatically create a changelog entry from the details below.
  • This Pull Request does not require a changelog entry. (Comment required below)
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Fix - Fixes an existing bug
  • Add - Adds functionality
  • Update - Update existing functionality
  • Dev - Development related task
  • Tweak - A minor adjustment to the codebase
  • Performance - Address performance issues
  • Enhancement - Improvement to existing functionality

Message

Changelog Entry Comment

Comment

Created manually: plugins/woocommerce/changelog/testops-234-products-block and plugins/woocommerce/client/blocks/changelog/testops-234-products-block.

Use of AI Tools

The migration was produced by an agent-run campaign with per-test mutation verification (see #68046). This PR was assembled, verified in isolation, and reviewed by an agent; the author reviewed the diff and takes responsibility for it.


🤖 Generated with Claude Code

The Products block is the deprecated `core/query` variation registered
under the `woocommerce/product-query` namespace. Its browser spec ran
seven Playwright identities, and four of them spent a real page load
proving contracts that never needed one.

Three of those four were archive-route parity checks that scraped two
title lists and asserted set equality. That assertion passes on two
matching empty arrays and says nothing about ordering. The fourth
repeated the add-to-cart journey character for character against a
published post instead of the archive template, so its only real delta
was the query mode.

This adds the two lower-layer owners the split needs. A Jest suite
covers `useAllowedControls`, the hook deciding which inspector controls
a Products block offers, across its inherited, re-rendered and Post
Editor branches. A PHPUnit method covers `ProductQuery::add_iapi_context`,
the per-item Interactivity wiring the add-to-cart button depends on,
asserting the exact namespace, key and decoded context on valid loop
items and proving three kinds of invalid item are skipped.

The retained Product Catalog title is rewritten rather than removed. It
now clicks through the Product Collection upgrade and asserts the
migrated query before saving and again after a reload, with cardinality
floors on both sides, which closes the old hole of two matching
singletons.

The owner for the three archive-route titles is
`RouteContextParityTest::test_product_collection_and_product_query_match_classic_route`.
Its Products arm was written by this batch's slice commit but lives in a
file batch 020 carries whole, so it ships in that pull request and is
not yet on trunk. This branch should land after it.

Consolidates the mega-branch slices:
- Slice 069: test(blocks): Reduce Products block browser coverage

Three later refinements to the retained parity title are folded in with
it: "Verify Product Collection query parity", "Require multi-product
query parity", and "Verify inherited collection parity".

Refs TESTOPS-234
Refs #68046

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added plugin: woocommerce Issues related to the WooCommerce Core plugin. focus: e2e tests Issues related to e2e tests labels Sep 12, 2026
@vladolaru
vladolaru marked this pull request as ready for review September 13, 2026 14:06
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Advanced

Run ID: 578df882-72fc-43e5-9226-7a630105f3cc

📥 Commits

Reviewing files that changed from the base of the PR and between 3eeb64f and 80a5a0b.

📒 Files selected for processing (6)
  • plugins/woocommerce/changelog/testops-234-products-block
  • plugins/woocommerce/client/blocks/assets/js/blocks/product-query/test/inspector-controls.tsx
  • plugins/woocommerce/client/blocks/changelog/testops-234-products-block
  • plugins/woocommerce/tests/e2e/tests/blocks/products/products.block_theme.spec.ts
  • plugins/woocommerce/tests/e2e/tests/blocks/products/utils.ts
  • plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductQuery.php

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Test using WordPress Playground

The changes in this pull request can be previewed and tested using a WordPress Playground instance.
WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Test this pull request with WordPress Playground.

Note that this URL is valid for 30 days from when this comment was last updated. You can update it by closing/reopening the PR or pushing a commit that changes plugin code.

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B 🆕

Total Size: 0 B

compressed-size-action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

focus: e2e tests Issues related to e2e tests plugin: woocommerce Issues related to the WooCommerce Core plugin.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant