Commit 9a99124b08e for woocommerce
commit 9a99124b08e7d9074cbcf0c57da0cebacb6b37df
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Tue Sep 15 19:43:18 2026 +0300
[tests] Demote the auto-generated shipping-class slug test to PHPUnit (#68641)
* test(shipping): Move generated shipping-class slugs below E2E
The shipping classes admin spec runs two browser titles from one
parametrized data set: adding a class with an explicit slug, and
adding one with a blank slug so the server generates it. Both drive
the same admin screen through the same steps; the only difference
between them is whether the slug field is filled.
What the second title actually proves is a server decision - that
woocommerce_shipping_classes_save_changes fills a blank slug from
the name, persists it, and returns it. That does not need a browser.
class-wc-ajax-test.php gains
test_shipping_classes_save_changes_generates_slug, which posts the
save-changes request with a blank slug and asserts the created term,
the generated slug, the description, and that the response lists the
new class exactly once.
The explicit-slug title stays, and it exercises the same admin
screen and the same rendering the removed one did. What is no longer
asserted is the rendered result for the blank-slug case
specifically: that the generated slug appears in the admin table.
The retained title still asserts that for a slug the test supplies.
class-wc-ajax-test.php is shared with another batch. This commit
takes trunk's copy and adds only the one method its own mega-branch
commit adds; the other four methods in that file's migration delta
ship separately in #68637. Trunk's copy already has the do_ajax
helper the new method uses, so it stands alone.
Two defects in the migrated test are fixed rather than carried. It
asserted the slug of a term it had just fetched by that slug, which
cannot fail; that assertion is replaced by one on the response row's
name, which nothing checked. And WC_Shipping::get_shipping_classes()
memoizes into a public property that nothing resets, so a warm cache
would omit the new term from the response and fail the count assertion
for an unrelated reason, while a cache left warm would show a deleted
class to later tests in the same process; the test now clears it before
the request and again in its teardown. The assertion count is unchanged
at eight.
The spec also drops an eslint-disable for playwright/valid-title.
That directive is already unused on trunk - it reports itself as
unused - so removing it removes a warning rather than adding one,
and it refers to the parametrized data row this commit changes.
Consolidates one mega-branch commit, subject verbatim:
- test(shipping): Move generated slugs below E2E
Refs TESTOPS-288
Refs #68046
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(php): Trim the shipping-class test cleanup to state the base leaks
`WP_Ajax_UnitTestCase::tear_down()` already clears `$_POST` and the
current user, `clean_up_global_scope()` clears `$_REQUEST` before the
next test, and the transaction rollback removes the term, so restoring
any of those by hand did nothing.
Two things do survive the base lifecycle and stay: `WC_Shipping`
memoizes the shipping class list on a public property, and the handler
assigns `$current_tab` and `$current_section` before firing
`woocommerce_update_options`.
Refs TESTOPS-288
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-288-shipping-classes b/plugins/woocommerce/changelog/testops-288-shipping-classes
new file mode 100644
index 00000000000..999d5c3f9d9
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-288-shipping-classes
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+Comment: Move the auto-generated shipping-class slug check from E2E to PHPUnit.
+
diff --git a/plugins/woocommerce/tests/e2e/tests/shipping/shipping-classes.spec.ts b/plugins/woocommerce/tests/e2e/tests/shipping/shipping-classes.spec.ts
index 8a81b16b2e8..5b876808750 100644
--- a/plugins/woocommerce/tests/e2e/tests/shipping/shipping-classes.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/shipping/shipping-classes.spec.ts
@@ -20,13 +20,6 @@ const uq = faker.string.alphanumeric( 5 );
description: `Small items that don't cost much to ship ${ uq }`,
testTitle: 'can add a shipping class with an unique slug',
},
- {
- name: `Poster Pack ${ uq }`,
- slug: '',
- expectedSlug: `poster-pack-${ uq }`,
- description: `Posters, stickers, and other flat items ${ uq }`,
- testTitle: 'can add a shipping class with an auto-generated slug',
- },
].forEach( ( { testTitle, name, slug, expectedSlug, description } ) => {
const test = baseTest.extend( {
storageState: ADMIN_STATE_PATH,
@@ -48,7 +41,6 @@ const uq = faker.string.alphanumeric( 5 );
},
} );
- // eslint-disable-next-line playwright/valid-title -- Title comes from the parametrized test data row.
test( testTitle, { tag: [ tags.SERVICES ] }, async ( { page } ) => {
await page.goto(
'wp-admin/admin.php?page=wc-settings&tab=shipping§ion=classes'
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
index 52e47c2c948..80d9edef239 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
@@ -141,6 +141,84 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
$this->assertEquals( $response['data']['message'], 'There was an error generating your API Key.' );
}
+ /**
+ * @testdox Saving a new shipping class with a blank slug generates and returns its persisted slug.
+ */
+ public function test_shipping_classes_save_changes_generates_slug(): void {
+ $had_current_tab = array_key_exists( 'current_tab', $GLOBALS );
+ $had_current_section = array_key_exists( 'current_section', $GLOBALS );
+ $original_current_tab = $GLOBALS['current_tab'] ?? null;
+ $original_current_section = $GLOBALS['current_section'] ?? null;
+ $name = 'Poster Pack ' . wp_unique_id();
+ $expected_slug = sanitize_title( $name );
+ $description = 'Posters, stickers, and other flat items.';
+
+ try {
+ $this->_setRole( 'administrator' );
+ $_POST = array(
+ 'wc_shipping_classes_nonce' => wp_create_nonce( 'wc_shipping_classes_nonce' ),
+ 'changes' => array(
+ 'new-row' => array(
+ 'newRow' => true,
+ 'name' => $name,
+ 'slug' => '',
+ 'description' => $description,
+ ),
+ ),
+ );
+ $_REQUEST = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Test installs the real nonce immediately above.
+
+ // WC_Shipping::get_shipping_classes() memoizes into a public property that nothing
+ // resets, so a warm cache would omit the new term from the response.
+ WC_Shipping::instance()->shipping_classes = array();
+
+ $response = $this->do_ajax( 'woocommerce_shipping_classes_save_changes' );
+ $this->assertTrue( $response['success'] ?? false, 'The registered AJAX action should report success.' );
+
+ $term = get_term_by( 'slug', $expected_slug, 'product_shipping_class' );
+ $this->assertInstanceOf( WP_Term::class, $term, 'The blank-slug request should create a shipping-class term.' );
+ if ( ! $term instanceof WP_Term ) {
+ throw new RuntimeException( 'The shipping-class term could not be reloaded.' );
+ }
+ $term_id = $term->term_id;
+
+ $this->assertSame( $name, $term->name );
+ $this->assertSame( $description, $term->description );
+
+ $response_rows = $response['data']['shipping_classes'] ?? array();
+ $matching_rows = array_values(
+ array_filter(
+ $response_rows,
+ static fn ( array $row ): bool => (int) ( $row['term_id'] ?? 0 ) === $term_id
+ )
+ );
+ $this->assertCount( 1, $matching_rows, 'The AJAX response should contain the new shipping class exactly once.' );
+ $this->assertSame( $expected_slug, $matching_rows[0]['slug'] );
+ $this->assertSame( $name, $matching_rows[0]['name'] );
+ $this->assertSame( $description, $matching_rows[0]['description'] );
+ } finally {
+ // Only state the base lifecycle does not own is restored here.
+ // `WP_Ajax_UnitTestCase::tear_down()` clears `$_POST` and the current
+ // user, `clean_up_global_scope()` clears `$_REQUEST` before the next
+ // test, and the transaction rollback removes the term. What survives
+ // is the shipping class list, which `WC_Shipping` memoizes on a public
+ // property, and the two settings globals the handler assigns before it
+ // fires `woocommerce_update_options`.
+ WC_Shipping::instance()->shipping_classes = array();
+
+ if ( $had_current_tab ) {
+ $GLOBALS['current_tab'] = $original_current_tab;
+ } else {
+ unset( $GLOBALS['current_tab'] );
+ }
+ if ( $had_current_section ) {
+ $GLOBALS['current_section'] = $original_current_section;
+ } else {
+ unset( $GLOBALS['current_section'] );
+ }
+ }
+ }
+
/**
* Test to verify that term color is saved in AJAX calls, but only for terms belonging to a visual attribute.
*