Commit 71ec4f9e600 for woocommerce

commit 71ec4f9e60090b4b36be08b2ab49ef213c4a2ff3
Author: Mike Jolley <mike.jolley@me.com>
Date:   Tue Jun 9 12:38:00 2026 +0100

    Fix milestone checkbox detection without markers (#64896)

    * Fix milestone checkbox detection without markers

    * Clarify milestone validation message and add edge-case tests

diff --git a/.github/workflows/scripts/assign-milestone-on-merge.js b/.github/workflows/scripts/assign-milestone-on-merge.js
index f3a2c429ecd..13b0d70a780 100644
--- a/.github/workflows/scripts/assign-milestone-on-merge.js
+++ b/.github/workflows/scripts/assign-milestone-on-merge.js
@@ -1,3 +1,5 @@
+const { getAutoAssignMilestoneSelection } = require('./milestone-selection');
+
 /**
  * Assigns a milestone to a merged PR based on the checkbox selection.
  *
@@ -21,16 +23,14 @@ module.exports = async ({ github, context, core }) => {
         return;
     }

-    const nextVersionMatch = body.match(/- \[([ xX])\].*\*\*.*next WooCommerce version.*\*\*/);
+    const nextVersionSelection = getAutoAssignMilestoneSelection(body);

-    if (!nextVersionMatch) {
+    if (!nextVersionSelection.found) {
         core.setFailed('Milestone selection checkbox not found in PR description. Cannot assign milestone.');
         return;
     }

-    const nextVersionChecked = nextVersionMatch[1].toLowerCase() === 'x';
-
-    if (!nextVersionChecked) {
+    if (!nextVersionSelection.checked) {
         core.setFailed('Auto-assign checkbox not selected. Cannot assign milestone.');
         return;
     }
diff --git a/.github/workflows/scripts/milestone-selection.js b/.github/workflows/scripts/milestone-selection.js
new file mode 100644
index 00000000000..68215b99f15
--- /dev/null
+++ b/.github/workflows/scripts/milestone-selection.js
@@ -0,0 +1,31 @@
+const AUTO_ASSIGN_MILESTONE_CHECKBOX_PATTERN = /(?:^|\r?\n)\s*[-*]\s*\[\s*([xX]?)\s*\][^\r\n]*Automatically assign milestone[^\r\n]*next WooCommerce version[^\r\n]*/;
+
+/**
+ * Finds the PR template checkbox used to request automatic milestone assignment.
+ *
+ * The pattern is non-global, so the first matching checkbox in the body wins.
+ * The PR template only ever renders this checkbox once, so first-match is the
+ * intended contract; duplicate occurrences (if any) are ignored.
+ *
+ * @param {string} body PR body.
+ * @return {{found: boolean, checked: boolean}} Checkbox state.
+ */
+const getAutoAssignMilestoneSelection = (body) => {
+    const match = (body || '').match(AUTO_ASSIGN_MILESTONE_CHECKBOX_PATTERN);
+
+    if (!match) {
+        return {
+            found: false,
+            checked: false,
+        };
+    }
+
+    return {
+        found: true,
+        checked: match[1].toLowerCase() === 'x',
+    };
+};
+
+module.exports = {
+    getAutoAssignMilestoneSelection,
+};
diff --git a/.github/workflows/scripts/milestone-selection.test.js b/.github/workflows/scripts/milestone-selection.test.js
new file mode 100644
index 00000000000..1e8cb32f869
--- /dev/null
+++ b/.github/workflows/scripts/milestone-selection.test.js
@@ -0,0 +1,80 @@
+const assert = require('node:assert/strict');
+const test = require('node:test');
+
+const { getAutoAssignMilestoneSelection } = require('./milestone-selection');
+
+const checkboxLine =
+    '- [x] Automatically assign milestone for the **[next WooCommerce version](../blob/trunk/plugins/woocommerce/woocommerce.php#L6)**';
+
+test('detects checked auto-assign checkbox with marker comments', () => {
+    const body = [
+        '### Milestone',
+        '',
+        '<!-- milestone-target-selection -->',
+        checkboxLine,
+        '<!-- /milestone-target-selection -->',
+    ].join('\n');
+
+    assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+        found: true,
+        checked: true,
+    });
+});
+
+test('detects checked auto-assign checkbox without marker comments', () => {
+    const body = [
+        '### Milestone',
+        '',
+        checkboxLine,
+        '',
+        '> **Note:** Check the box above to have the milestone automatically assigned when merged.',
+    ].join('\n');
+
+    assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+        found: true,
+        checked: true,
+    });
+});
+
+test('detects unchecked auto-assign checkbox', () => {
+    const body = checkboxLine.replace('[x]', '[ ]');
+
+    assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+        found: true,
+        checked: false,
+    });
+});
+
+test('does not detect unrelated checklist items', () => {
+    const body = '- [x] I have tested the next WooCommerce version locally.';
+
+    assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+        found: false,
+        checked: false,
+    });
+});
+
+test('detects checked auto-assign checkbox with "*" bullet and uppercase marker', () => {
+    const body = checkboxLine.replace('- [x]', '* [X]');
+
+    assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+        found: true,
+        checked: true,
+    });
+});
+
+test('detects indented auto-assign checkbox with CRLF line endings', () => {
+    const body = ['### Milestone', '', `    ${checkboxLine}`, ''].join('\r\n');
+
+    assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+        found: true,
+        checked: true,
+    });
+});
+
+test('returns not found for null or undefined body', () => {
+    const expected = { found: false, checked: false };
+
+    assert.deepEqual(getAutoAssignMilestoneSelection(null), expected);
+    assert.deepEqual(getAutoAssignMilestoneSelection(undefined), expected);
+});
diff --git a/.github/workflows/scripts/validate-milestone-selection.js b/.github/workflows/scripts/validate-milestone-selection.js
index 4b700f69e00..1b11bce2555 100644
--- a/.github/workflows/scripts/validate-milestone-selection.js
+++ b/.github/workflows/scripts/validate-milestone-selection.js
@@ -1,3 +1,5 @@
+const { getAutoAssignMilestoneSelection } = require('./milestone-selection');
+
 /**
  * Validates that a PR has either a milestone set or the auto-assign checkbox selected.
  *
@@ -21,21 +23,14 @@ module.exports = async ({ github, context, core }) => {

     const body = pr.body || '';

-    if (!body.includes('<!-- milestone-target-selection -->')) {
-        core.setFailed('Milestone selection section not found in PR description. Please use the PR template and select the milestone option, or manually assign a milestone.');
-        return;
-    }
+    const nextVersionSelection = getAutoAssignMilestoneSelection(body);

-    const nextVersionMatch = body.match(/- \[([ xX])\].*\*\*.*next WooCommerce version.*\*\*/);
-
-    if (!nextVersionMatch) {
-        core.setFailed('Milestone selection checkbox not found or modified. Please restore the original checkbox format from the PR template.');
+    if (!nextVersionSelection.found) {
+        core.setFailed('Auto-assign milestone checkbox not found. Please add the milestone checkbox from the PR template, or manually assign a milestone.');
         return;
     }

-    const nextVersionChecked = nextVersionMatch[1].toLowerCase() === 'x';
-
-    if (!nextVersionChecked) {
+    if (!nextVersionSelection.checked) {
         core.setFailed('No milestone option selected. Please check the auto-assign checkbox or manually assign a milestone.');
         return;
     }