Skip to content

Feature/bulk editor ph2 - #23552

Open
vraja-pro wants to merge 264 commits into
trunkfrom
feature/bulk-editor-ph2
Open

Feature/bulk editor ph2#23552
vraja-pro wants to merge 264 commits into
trunkfrom
feature/bulk-editor-ph2

Conversation

@vraja-pro

@vraja-pro vraja-pro commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Context

Summary

This PR can be summarized in the following changelog entry:

  • Adds the Yoast bulk editor to the WordPress admin bulk-actions menu, with needs-improvement filtering, smart selection, and AI-powered SEO title and description generation.

Relevant technical choices:

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

This PR can be acceptance tested by following the steps per feature area below.


1. Bulk edit entry in WP admin bulk actions — #23488

Setup
  • Use a site with at least 25 published posts and a handful of pages.
  • Log in as an Administrator.
  • On Posts → All Posts, open Screen Options (top right) and set Number of items per page to 30, so you can select more than 20 posts on one page.
The dropdown entry
  • Go to Posts → All Posts and open the Bulk actions dropdown.
  • Confirm it contains a "Yoast SEO" group after the default actions (Edit, Move to Trash), with a single entry "Bulk edit ↗".
  • Confirm the arrow is a plain text glyph, not an emoji image (it should look like the surrounding text, not a colored icon).
  • Confirm the same entry appears in the bottom bulk-actions dropdown of the list table.
  • Open the Trash view (Posts → All Posts → Trash): the "Yoast SEO" group must not be there.
Carry a small selection over (happy path)
  • On Posts → All Posts, tick 3 posts, pick Bulk edit and click Apply.
  • You land on the Yoast bulk editor with the Posts tab active.
  • The list shows exactly those 3 posts, all pre-checked, and the selection toolbar reads 3 selected.
  • Open the Filters popover: an "Overview selection" checkbox is ticked, above the status filters, and it is counted in the filter badge.
  • No notice about "first 20" is shown (the selection fits).
  • Good to know: the bulk editor sorts by ID (newest first) while the overview sorts by date, so the order of the rows may differ — that is expected.
URL cleanup (one-shot parameters)
  • Right after landing on the bulk editor via the bulk action, the post_ids and selected_count parameters are removed automatically shortly after the page loads (content_type stays).
  • Refresh the page: you get a clean bulk editor — full list, no "Overview selection" filter, nothing pre-checked.
The "Overview selection" filter
  • Carry 3 posts over again. In the Filters popover, untick "Overview selection": the full post list returns and the selection is cleared.
  • Tick it back on: the list narrows to the carried posts again, but the selection stays cleared (re-ticking restores the filter, not the selection).
  • Carry 3 posts over again, then switch to the Pages tab and back: the filter and the carried selection are discarded.
More than 20 selected: the truncation notice
  • On Posts → All Posts (30 per page), tick 25 posts, pick Bulk edit, click Apply.
  • In the bulk editor, only the first 20 arrive: 20 rows listed and pre-checked, "20 selected".
  • An info notice renders inside the table header, between the selection toolbar row and the "Generate SEO titles"/"Generate meta descriptions" row: "Only the first 20 posts from your selection were carried over. The bulk editor supports up to 20 posts at a time."
  • The noun matches the content type (carry >20 pages over and it reads "pages").
  • Dismiss the notice (X): it disappears, and if you have no selection and no unsaved edits the row it occupied collapses.
  • Repeat with exactly 20 selected: no notice.
The exclusion notice (carried posts the bulk editor cannot show)
  • Edit one post and set its visibility to Private (or give it a password). Make sure you have at least 2 normal published posts as well.
  • On Posts → All Posts, tick the private post plus 2 published posts, pick Bulk edit and click Apply.
  • In the bulk editor, only the 2 published posts are listed and pre-checked, and the count reads "2 of 2 posts selected" — the private post was dropped.
  • An info notice explains why: "Your selection has been updated. Private, password-protected, or non-indexed posts can't be bulk edited and were excluded."
  • The noun follows the content type.
  • Click the notice's X: it disappears.
  • Counter-check: carry over only published posts — the notice must not appear.
Both notices at the same time (>20 selected AND some excluded)
  • On Posts → All Posts (30 per page), make sure a private post sits among the newest 20 posts, tick 25 posts including the private one, pick Bulk edit and click Apply.
  • In the bulk editor, two stacked notices appear between the selection toolbar and the Generate row:
    • "Only the first 20 posts from your selection were carried over. The bulk editor supports up to 20 posts at a time."
    • "Your selection has been updated. Private, password-protected, or non-indexed posts can't be bulk edited and were excluded."
  • The selected count reads "19 of 19 posts selected".
  • Dismiss the exclusion notice (bottom X): only it disappears; the other notice stays and the row stays open.
  • Dismiss the remaining notice too: with nothing else occupying the row, it collapses.
Permissions
  • Log in as an Editor (no wpseo_manage_options capability).
  • On Posts → All Posts, open the bulk-actions dropdown: the "Yoast SEO" group is absent.
Other content types
  • Repeat the happy path on Pages → All Pages: the entry is there, and applying it lands on the bulk editor with the Pages tab active and the carried pages pre-checked.
  • With WooCommerce active, check the Products overview the same way.
  • A post type that the bulk editor does not support must not get the entry on its overview.
Hand-crafted URLs (defensive handling)
  • Open the bulk editor with an invalid content type: wp-admin/admin.php?page=wpseo_page_bulk_edit&content_type=bogus&post_ids=1,2,3&selected_count=3.
  • The page loads normally on the default content type with no carried selection, no filter, no notice, and no console errors.
  • Try post_ids=abc,-5,0: same clean result.
(Optional) RTL
  • Set the admin language to an RTL language (e.g. Hebrew) in your user profile.
  • The dropdown entry shows a mirrored arrow (↖), rendered at the reading end of the label.

2. "Needs improvement" filter — #23438

Setup and migration
  • Build the plugin (yarn build / grunt build) and activate it. On activation the AddSeoTitleAndMetaDescriptionScores migration runs, adding the seo_title_score and meta_description_score columns to wp_yoast_indexable. Confirm both columns exist.
  • Make sure SEO analysis is enabled: Yoast SEO → General → Features → SEO analysis (on by default).

On a dev checkout, force the pending migrations with WP-CLI (inside the Docker container):

wp eval '$runner = YoastSEO()->classes->get( Yoast\WP\SEO\Initializers\Migration_Runner::class ); $runner->run_migrations( "free", "99.0.0" );'

Verify the columns:

wp eval 'global $wpdb; $t = $wpdb->prefix . "yoast_indexable"; var_export( $wpdb->get_col( "SHOW COLUMNS FROM {$t}" ) );'
Filter dropdown appearance

Open the Filters dropdown on the Search appearance tab. Each "needs improvement" option ("SEO titles", "Meta descriptions") should show a round red score bullet (#dc3232, ~12px) before its label, separated from the status filters by a divider, and with no visible "Needs improvement" heading (it exists only for screen readers).

The filter — empty fields
  • Apply "SEO title needs improvement": posts with an empty SEO title appear. Repeat for "Meta description needs improvement".
  • On the Social appearance tab, apply "Social title / description needs improvement": only posts with an empty social title / description match (social fields are matched on emptiness only).
The filter — weak scores (search tab only)

Craft a post that NEEDS improvement (non-empty, to exercise the score path). Set a focus keyphrase (e.g. banana bread), then:

  • SEO title = a normal-length title that does not contain the keyphrase, e.g. Delicious homemade loaf.
  • Meta description = ~130 characters that do not contain the keyphrase.

Open/save the post so the scores persist. In the bulk editor, both "SEO title needs improvement" and "Meta description needs improvement" now list it.

Craft a post that does NOT need improvement. Same keyphrase, but SEO title beginning with the exact keyphrase and a meta description containing the keyphrase. Neither filter lists this post.

Updating the score from a manual edit
  • Edit an SEO title inline in the bulk editor and save it. The Network tab shows a POST yoast/v1/bulk_editor/update_scores; refresh and confirm the row leaves or joins the filtered set according to its new score. Editing the focus keyphrase re-scores both the SEO title and the meta description.
When SEO analysis is disabled — site-wide
  • Turn SEO analysis off in Yoast SEO → General → Features. The filter's score half is ignored: only empty fields match. The bulk editor sends no update_scores request.
When SEO analysis is disabled — per-user
  • Re-enable site-wide, then disable on your own user profile. Bulk editor filter and re-scoring follow the site-wide setting — other posts still filter by score.
RTL

Switch to an RTL language and reopen the Filters dropdown. The score bullet uses logical spacing: it sits before its label on the right-hand side, and the checkbox / bullet / label order reads right-to-left.


3. Smart-select posts that need improvement — #23470

  • Go to Yoast SEO > Tools > Bulk editor.
  • Open the Select menu and confirm two new options appear below a divider, each with a red needs improvement dot: SEO titles and Meta descriptions.
  • Click SEO titles and confirm only the rows whose SEO title needs improvement become selected.
  • Clean the selection, filter posts with SEO title improvement, then choose SEO titles from Select. All filtered posts should now be selected (max. 20 per page).
  • Click Meta descriptions and confirm the selection matches the rows whose meta description needs improvement.
  • Switch to the Social appearance tab, open Select, and confirm the two options relabel to Social titles / Social descriptions and select rows whose social field is empty.
  • Confirm the selection is cleared when the context changes: switch between tabs (selection resets on switch) and toggle a needs improvement option in Filters (selection resets there too).
  • Confirm each option's accessible name reads the full action (e.g. Select pages with meta descriptions that need improvement) while the visible label stays short.

4. Clear "needs improvement" filter on tab switch — #23540

  • Go to Yoast SEO > Tools > Bulk editor.
  • On the Search appearance tab, open Filters and check SEO titles under Needs improvement. Confirm the list narrows and the Filters button shows a count badge of 1.
  • Page through to page 3 of the filtered results.
  • Click the Social appearance tab. Confirm the filter is gone: no count badge on Filters, both Needs improvement boxes unchecked, the full list shown, and you are back on page 1.
  • Reverse: on Social appearance, check Social descriptions, then switch to Search appearance. Confirm the filter is cleared the same way.
  • Confirm other filters survive the switch: type a search term, check a status such as Draft, then switch tabs. Both should still be applied — only Needs improvement clears.
  • Confirm a plain tab switch does not lose your page: with no Needs improvement filter active, go to page 3 and switch tabs — you should stay on page 3.

In the console, wp.data.select( "@yoast/bulk-editor" ).selectNeedsImprovement() should return [] right after a tab switch, and the Network tab should show exactly one bulk_editor/posts request for that switch, without a needs_improvement[] parameter.


5. Reset search and filters when switching content types — #23514

  • Open the Bulk Editor.
  • Navigate to any content type (e.g. Pages).
  • Apply one or more filters (e.g. SEO score status).
  • Enter a value in the search field and perform a search.
  • Switch to a different content type (e.g. Posts).
  • Verify the search field is empty and all filters are cleared.

6. Default template when SEO title/description is empty — #23538

Setup

  • Go to SEO > Settings > Search Appearance and confirm the SEO title template for Posts is set — e.g. %%title%% %%sep%% %%sitename%%.
  • Create a brand-new post and publish it without saving any SEO title, meta description, social title, or social description explicitly.

Verify the Search appearance tab

  • Go to SEO > Bulk Editor → Search appearance tab.
  • Find the post: the SEO title column should show the post type's SEO title template as styled replacement variable pills, not empty.

Verify the Social appearance tab

  • Switch to the Social appearance tab.
  • The Social title column should show the social title template as a pill (e.g. a single Title pill from %%title%%), not empty.

Verify the edit textarea and save behaviour

  • Edit the SEO title or meta description. The textarea field should show the replacement variable pills.
  • Without typing anything, click Save. The column should still show the same template pills — the post should not have had a value baked into its meta (verify via the single-post editor that SEO title is still blank).

Verify the needs-improvement filter

  • Use the Needs improvement filter and select SEO title. The post from step 2 should not appear — its fallback template is non-empty, so it does not need improvement.

Regression

  • Go to Yoast SEO → Settings → Content types → Posts, check the Search appearance, and test the replacement variables: insert a replacement variable and save. Repeat for social appearance.

7. Focus keyphrase desync fix — #23534

  • Go to SEO → Bulk editor → Search appearance tab.
  • Click Edit on any post row.

Comma warning

  • In the focus keyphrase field, type a value containing a comma, e.g. seo, ranking.
  • Verify a warning: "Are you trying to use multiple keyphrases? You should add them separately in the editor."

Length warning

  • Clear the field and paste a string longer than 191 characters.
  • Verify a warning: "You reached the maximum limit of 191 characters."

HTML warning + backend sync (the core fix)

  • Clear the field and type a keyphrase containing an HTML tag, e.g. <b>seo</b>.
  • Verify a warning: "Your keyphrase contains HTML tags that will be stripped on save."
  • Click Save for that row.
  • Verify that after saving, the focus keyphrase cell displays seo — not <b>seo</b>. The frontend must reflect what the server actually stored.
  • Reload the page and confirm the focus keyphrase for that post still shows seo.
  • For all cells, check the color of content in the cells when not editing is rgba(71, 85, 105, 1).

TextareaField impact check

  • Enable Yoast SEO Premium and go to Yoast SEO → Settings → Site representations → Additional organization info → Organization description.
  • Check the label Organization description is rendered.

8. Shift+click range selection and animated notices banner — #23511

  • Open the bulk editor with multiple posts.
  • Click a checkbox on one row (plain click) — it should select.
  • Shift+click a checkbox several rows below — all rows in between should be selected.
  • Click "Select all" then "Deselect all" — anchor should reset (next shift+click starts a new range from the next plain click).
  • With Premium active, run an AI generation that skips posts without a focus keyphrase. Observe the banner sliding in smoothly (no layout jump). Dismiss it and confirm it slides out.

9. AI content tip on visible text — #23507

Prerequisites: Yoast SEO with AI generation available and consent granted.

Scenario Steps Expected result
Markup-heavy short post (the bug) Create a post with a focus keyphrase whose body is ~7 short paragraph blocks each holding one or two words (well under 300 characters of visible text, but over 300 characters of stored markup). Open the AI title or description generator. The tip "Improve the accuracy of your generated AI titles/descriptions by writing more content in your page" IS shown. Before this fix it was not.
Genuinely long post A post with a focus keyphrase and well over 300 characters of body text. Open the generator. No tip.
Genuinely short post A post with a focus keyphrase and a single short sentence. Open the generator. The tip is shown.
Product threshold With WooCommerce active, a product with between 150 and 300 characters of visible text. Open the generator. No tip — products use the lower 150-character threshold.
Dismissal With the tip shown, click "Don't show again", then reopen the generator. The tip stays hidden. "Dismiss" hides it only for the current modal session.
Classic editor Repeat the markup-heavy and long-post cases in the classic editor. Same results.

10. AI prompt content collection — #23524

A. New REST route

  • On the bulk editor page, grab a nonce from the console: window.wpseoBulkEditorData.nonce, then send a GET request:
    /wp-json/yoast/v1/bulk_editor/posts_content?ids=<id1>,<id2>
    Expected: returns { posts: [ { id, content } ] } with raw, unrendered post_content.
  • Confirm the bulk editor table page itself does not request content: reload with the Network tab open and check that no posts_content request is made.

B. Bridge exposed to Premium

  • In the console on the bulk editor page, check:
Object.keys( window.yoast.bulkEditor.helpers )   // preparePromptContent, getVisibleContentLength
window.yoast.bulkEditor.constants                // { MAX_TOKENS_DEFAULT: 300, MAX_TOKENS_IRREGULAR: 150 }
window.wpseoBulkEditorData.analysis.shortcodes   // registered tags: caption, gallery, …
  • Confirm the shortcode list makes the parser keep enclosed text:
await window.yoast.bulkEditor.helpers.preparePromptContent( "[caption]Wrapped wording stays.[/caption]" )

Expected: "Wrapped wording stays.".

C. Regressions to watch

  • In-editor AI generator: open a post with a focus keyphrase, click Generate meta description, and confirm suggestions still appear and are based on the opening sentences.
  • In-editor AI tip: confirm the "not enough content" tip appears for a very short post and not for a long one (see also fix(ai-generator): measure the content tip on visible text, not editor markup #23507 above).
  • Bulk editor field re-scoring: edit an SEO title or meta description in the bulk editor by adding the focus keyphrase and confirm its score changes via the filter or smart selection.

11. Premium version update notification modal — #23546

  • Install Yoast SEO Premium version 27.9 or 28.0.
  • Go to Yoast SEO → Tools → Bulk editor, select a few posts, and click the AI buttons.
  • Confirm you see the modal to update Premium.
  • Change the site language to an RTL language (e.g. Arabic or Hebrew): confirm the "Update now" button arrow is aligned to the left.
  • Click the close buttons and confirm the modal closes. Reopen and click the X button — same.
  • Reopen and click "Update now": confirm you are redirected to the plugins page and Premium is being updated.
  • Deactivate Premium, go back to the bulk editor, select a few posts, and click the AI buttons — confirm the upsell appears.
  • Activate Premium version 28.1 or above, go back to the bulk editor, select a few posts, click the AI buttons — confirm the title or description is generated.

Regression for the AI generator when editing a post

  • Edit a post with a focus keyphrase on a local environment or without subscriptions.
  • Confirm you get the Danger modal as before.

12. MyYoast connection data — #23461

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and committed the results, if my PR introduces or edits images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes https://github.com/Yoast/reserved-tasks/issues/1414

FAMarfuaty and others added 30 commits July 15, 2026 15:52
After a search-appearance save the bulk editor re-runs the analysis worker on the rendered fields, derives the SEO title and meta description scores, and persists them, so the needs-improvement filter reflects edits without reopening the post editor. Enqueues the worker script data on the bulk editor page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ad of the worker

Re-scoring after an edit now runs only the SEO title and meta description assessors on the main thread, instead of spinning up the analysis worker and running the full page analysis to read four results. A JS filter (yoast.bulkEditor.analysis.researcher) lets Premium return a researcher with morphology data; Free scores with base word forms, matching the Free editor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lysis is disabled

The needs-improvement filter only matches on the stored per-field score while SEO analysis is enabled; when it is off the score is no longer maintained, so the search fields fall back to the empty-field check (like the social fields).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After a search-appearance save the bulk editor re-scores the SEO title and meta description on the rendered field values and persists the result, so the needs-improvement filter reflects edits without reopening the post editor. Scoring runs the two field assessors directly on the main thread (no analysis worker, no full-page analysis); a JS filter (yoast.bulkEditor.analysis.configureResearcher) lets Premium augment the researcher with morphology data, while Free scores with base word forms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lysis is disabled

The needs-improvement filter only matches on the stored per-field score while SEO analysis is enabled; when it is off the score is no longer maintained, so the search fields fall back to the empty-field check (like the social fields).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-filter-dropdown' of github.com:Yoast/wordpress-seo into 1346-bulk-editor-add-needs-improvement-filter-option-to-filter-dropdown
Injects MyYoast_Connection_Conditional, Status_Presenter, and
Connection_Permission into Bulk_Editor_Integration and exposes a
myyoastConnection payload so the JS side can pick the correct
"Yoast AI cannot reach your site" notification variant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Applying an AI-generated SEO title or meta description now recomputes just
that field's score via createSingleFieldScorer, wired through onFieldApplied,
so the needs-improvement filter reflects AI edits the same as manual ones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…core dot

Relabels the options to the plain field names and adds a decorative red
(#dc3232) score dot; a visually-hidden group legend keeps the "needs
improvement" meaning for assistive tech.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both collectors duplicated the 1-70 "needs improvement" band (MIN/MAX ints
in the indexable collector, a [1,70] array in the post-meta fallback). Move
it to Posts_Collector_Interface::NEEDS_IMPROVEMENT_MIN_SCORE / MAX_SCORE and
reference it from both, so the band lives in one place. Also aligns the
$needs_improvement @PARAM wording with the property doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduced a new property to the Post class to track whether each field requires improvement, keyed by field parameter.
The double/mock classes mirror the real classes' properties; enforcing
native/@var type hints on the mock's own untyped property declarations
adds no value, so exclude tests/*/Doubles/* from the PropertyTypeHint
sniff, matching the existing Doubles exclusions for the comment sniffs.

Lower the phpcs ERRORS threshold to 2244 to lock in the errors this
exclusion removes across the test doubles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r args

Adds mocks and assertions for MyYoast_Connection_Conditional,
Status_Presenter, and Connection_Permission in the abstract base and
Constructor_Test to match the expanded constructor signature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mocks the feature-flag conditional as disabled so get_myyoast_connection_data
returns null, and adds the myyoastConnection key to the expected script data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the is_bool + boolean AND pattern with a strict === true check,
and adds Get_Myyoast_Connection_Data_Test covering the flag-off, provisioned,
not-provisioned, and non-boolean is_provisioned paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s-improvement-filter-option-to-filter-dropdown
@github-actions

Copy link
Copy Markdown

📦 Plugin zip built successfully!

Download it from the workflow run.

vraja-pro and others added 29 commits August 14, 2026 13:18
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…47-add-notification-modal-when-free-is-updated-but-premium-is-not-with-a-one-click-premium-update-option
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… premiumUpdateUrl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SEO Managers have wpseo_manage_options but not update_plugins, so sending
them to update.php results in a wp_die permission error. The update URL is
now only exposed to users who can actually complete the update, and the
"Update now" button in the modal is hidden when the URL is absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a current_user_can expectation to the existing Enqueue_Assets_Test
(previously the function was never called; my prior commit added the check
and left the expectation unset). Adds Get_Premium_Update_Url_Test covering
the admin path (URL returned) and the non-admin path (empty string). Adds
update-modal.test.js covering heading/body rendering, the Update Now link
with its attributes, the SEO Manager case where the link is absent, and
the close-button callback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…_Url_Test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecessary store subscriptions

Drop useAiUpsell from FreeBulkActions and pass contentType to UpsellModal instead, so the
hook's store subscriptions are only created when the modal is actually mounted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UpsellModal now calls useAiUpsell internally rather than accepting the upsell values as props.
Mock the hook via jest.fn() so individual tests can control its return value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_script_data() now calls get_premium_update_url(), which calls current_user_can().
Stub it to return false so the method short-circuits and Brain\Monkey does not
throw a MissingFunctionExpectations error. Also removed the unused wp_nonce_url stub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Premium's notice fills (error, notice, minimal-content) all pull their data from the
store via useSelect and do not consume fillProps. Remove the five dead props from
BulkActionsNotices and BulkActions (contentTypeLabel, contentTypeSingularLabel,
selectedIds, activeFieldSet, contentType), and the corresponding pass-through in
BulkEditorContent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of threading a ref through SelectionToolbar → BulkEditorContent →
DismissibleAlert, DismissibleAlert now reads the active field set from the
store and focuses the Select menu button by id. SelectMenu exposes an id
prop so its trigger button gets a stable, predictable id; SelectionToolbar
becomes a plain function component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…files

DismissibleAlert now calls select(STORE_NAME) so any test that renders it
needs the store registered. Move registerStore to a top-level beforeAll so
it runs before every describe block in the file, not just the second one.
Also remove the forwardRef mock from initialize.test.js since SelectionToolbar
is no longer a forwardRef component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Premium is not active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-free-is-updated-but-premium-is-not-with-a-one-click-premium-update-option

1347 add notification modal when free is updated but premium is not with a one click premium update option
Co-authored-by: Aida Marfuaty <48715883+FAMarfuaty@users.noreply.github.com>
…08-bulk-editor-aria-hidden-warning-when-dismissing-notification-banner
…est setup

Rename the draft useActionBarFocusTrap hook to useActionBarFocusReturn
(focus-trap implies preventing focus from leaving; this moves focus back).
API now returns a plain (e) => void callback: focuses the Select menu
button by id, falls back to e.currentTarget.blur() when not found.
DismissibleAlert calls focusActionBar(e) then onDismiss(), removing its
direct store and STORE_NAME imports.

Fix bulk-action-bar.test.js: useSelect is fully mocked there, so the
real store never needed to be registered. Remove the registerStore import
and beforeAll, and mock useActionBarFocusReturn so DismissibleAlert
does not try to call selectActiveFieldSet through the mocked useSelect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix a missing hyphen in useActionBarFocusReturn: activeFieldSet is the
bare value ("search"/"social") while the button id is built with a
leading-hyphen idSuffix, so the lookup must be
yst-bulk-editor-select-menu-${activeFieldSet}-button. Without the
hyphen getElementById always returned null and focus never moved.

Clarify the select-menu id JSDoc: props.id is a prefix, not the button
id itself (the trigger gets ${id}-button).

Add a document.activeElement assertion after each Dismiss click in the
BulkEditorContent integration tests — this would have caught the id
typo before merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ning-when-dismissing-notification-banner

Fix aria-hidden warning and clean up unused BULK_NOTICES_SLOT fillProps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants