• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 5ab28f429a427cb08d455a3e5a8c5d3acfefb5c0

16 Jan 2026 08:31AM UTC coverage: 53.29% (-0.03%) from 53.315%
5ab28f429a427cb08d455a3e5a8c5d3acfefb5c0

Pull #22872

github

web-flow
Merge ea546b395 into d394743dd
Pull Request #22872: Fix keyboard focus management for AI Optimize button and toast notifications

8785 of 16304 branches covered (53.88%)

Branch coverage included in aggregate %.

12 of 51 new or added lines in 10 files covered. (23.53%)

64 existing lines in 8 files now uncovered.

32864 of 61852 relevant lines covered (53.13%)

46618.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/js/src/components/contentAnalysis/ReadabilityAnalysis.js
1
/* global wpseoAdminL10n */
2
/* External components */
3
import { Component, Fragment } from "@wordpress/element";
4
import { withSelect } from "@wordpress/data";
5
import PropTypes from "prop-types";
6
import styled from "styled-components";
7
import { __, sprintf } from "@wordpress/i18n";
8
import { isNil } from "lodash";
9

10
/* Internal components */
11
import ScoreIconPortal from "../portals/ScoreIconPortal";
12
import Results from "../../containers/Results";
13
import Collapsible from "../SidebarCollapsible";
14
import getIndicatorForScore from "../../analysis/getIndicatorForScore";
15
import { getIconForScore } from "./mapResults";
16
import { LocationConsumer, RootContext } from "@yoast/externals/contexts";
17
import HelpLink from "../HelpLink";
18
import ReadabilityResultsPortal from "../portals/ReadabilityResultsPortal";
19
import { isWordComplexitySupported } from "../../helpers/assessmentUpsellHelpers";
20
import { addQueryArgs } from "@wordpress/url";
21
import getL10nObject from "../../analysis/getL10nObject";
22
import AIOptimizeButton from "../../ai-optimizer/components/ai-optimize-button";
23
import { shouldRenderAIOptimizeButton } from "../../helpers/shouldRenderAIOptimizeButton";
24
import AIButtonFocusWrapper from "./AIButtonFocusWrapper";
25

26
const AnalysisHeader = styled.span`
×
27
        font-size: 1em;
28
        font-weight: bold;
29
        margin: 0 0 8px;
30
        display: block;
31
`;
32

33
const ReadabilityResultsTabContainer = styled.div`
×
34
        padding: 16px;
35
`;
36

37
const StyledHelpLink = styled( HelpLink )`
×
38
        margin: -8px 0 -4px 4px;
39
`;
40

41
/**
42
 * Redux container for the readability analysis.
43
 */
44
class ReadabilityAnalysis extends Component {
45
        /**
46
         * Renders the Readability Analysis results.
47
         *
48
         * @param {Array} upsellResults The array of upsell results.
49
         *
50
         * @returns {wp.Element} The Readability Analysis results.
51
         */
52
        renderResults( upsellResults ) {
53
                const highlightingUpsellLink = "shortlinks.upsell.sidebar.highlighting_readability_analysis";
×
54

55
                return (
×
56
                        <Fragment>
57
                                <AnalysisHeader>
58
                                        { __( "Analysis results", "wordpress-seo" ) }
59
                                        <StyledHelpLink
60
                                                href={ wpseoAdminL10n[ "shortlinks.readability_analysis_info" ] }
61
                                                className="dashicons"
62
                                        >
63
                                                <span className="screen-reader-text">
64
                                                        {
65
                                                                /* translators: Hidden accessibility text. */
66
                                                                __( "Learn more about the readability analysis", "wordpress-seo" )
67
                                                        }
68
                                                </span>
69
                                        </StyledHelpLink>
70
                                </AnalysisHeader>
71
                                <Results
72
                                        results={ this.props.results }
73
                                        upsellResults={ upsellResults }
74
                                        marksButtonClassName="yoast-tooltip yoast-tooltip-w"
75
                                        marksButtonStatus={ this.props.marksButtonStatus }
76
                                        highlightingUpsellLink={ highlightingUpsellLink }
77
                                        shouldUpsellHighlighting={ this.props.shouldUpsellHighlighting }
78
                                        renderAIOptimizeButton={ this.renderAIOptimizeButton }
79
                                />
80
                        </Fragment>
81
                );
82
        }
83

84
        /**
85
         * Returns the list of results used to upsell the user to Premium.
86
         *
87
         * @param {string} location                 Where this component is rendered (metabox or sidebar).
88
         * @param {string} locationContext         In which editor this component is rendered.
89
         *
90
         * @returns {Array} The upsell results.
91
         */
92
        getUpsellResults( location, locationContext ) {
93
                let link = wpseoAdminL10n[ "shortlinks.upsell.metabox.word_complexity" ];
×
94
                if ( location === "sidebar" ) {
×
95
                        link = wpseoAdminL10n[ "shortlinks.upsell.sidebar.word_complexity" ];
×
96
                }
97

98
                link = addQueryArgs( link, { context: locationContext } );
×
99

100
                /*
101
                 * We don't show the upsell for Word complexity assessment if it's not supported for the current locale.
102
                 */
103
                if ( ! isWordComplexitySupported() ) {
×
104
                        return [];
×
105
                }
106

107
                const wordComplexityUpsellText = sprintf(
×
108
                        /* Translators: %1$s is a span tag that adds styling to 'Word complexity', %2$s is a closing span tag.
109
                           %3$s is an anchor tag with a link to yoast.com, %4$s is a closing anchor tag.*/
110
                        __(
111
                                "%1$sWord complexity%2$s: Is your vocabulary suited for a larger audience? %3$sYoast SEO Premium will tell you!%4$s",
112
                                "wordpress-seo"
113
                        ),
114
                        "<span style='text-decoration: underline'>",
115
                        "</span>",
116
                        `<a href="${ link }" data-action="load-nfd-ctb" data-ctb-id="f6a84663-465f-4cb5-8ba5-f7a6d72224b2" target="_blank">`,
117
                        "</a>"
118
                );
119

120
                return [
×
121
                        {
122
                                score: 0,
123
                                rating: "upsell",
124
                                hasMarks: false,
125
                                id: "wordComplexity",
126
                                text: wordComplexityUpsellText,
127
                                markerId: "wordComplexity",
128
                        },
129
                ];
130
        }
131

132

133
        /**
134
         * Renders the Yoast AI Optimize button.
135
         * The button is shown when:
136
         * - The assessment can be fixed through Yoast AI Optimize.
137
         * - The AI feature is enabled (for Yoast SEO Premium users; for Free users, the button is shown with an upsell).
138
         * - We are in the block editor or classic editor.
139
         * - We are not in the Elementor editor, nor in the Elementor in-between screen.
140
         * - We are not in a Taxonomy.
141
         *
142
         * @param {boolean} hasAIFixes Whether the assessment can be fixed through Yoast AI Optimize.
143
         * @param {string} id The assessment ID.
144
         *
145
         * @returns {void|JSX.Element} The AI Optimize button, or nothing if the button should not be shown.
146
         */
147
        renderAIOptimizeButton = ( hasAIFixes, id ) => {
×
148
                const { isElementor, isAiFeatureEnabled, isTerm } = this.props;
×
149
                const isPremium = getL10nObject().isPremium;
×
150

151
                // Don't show the button if the AI feature is not enabled for Yoast SEO Premium users.
152
                if ( isPremium && ! isAiFeatureEnabled ) {
×
153
                        return;
×
154
                }
155
                const shouldRenderAIButton = shouldRenderAIOptimizeButton( hasAIFixes, isElementor, isTerm );
×
156
                // Show the button if the assessment can be fixed through Yoast AI Optimize, and we are not in the Elementor editor, or Taxonomy.
157
                return shouldRenderAIButton && ( <AIOptimizeButton id={ id } isPremium={ isPremium } /> );
×
158
        };
159

160

161
        /**
162
         * Renders the Readability Analysis component.
163
         *
164
         * @returns {wp.Element} The Readability Analysis component.
165
         */
166
        render() {
167
                const score = getIndicatorForScore( this.props.overallScore );
×
168

169
                if ( isNil( this.props.overallScore ) ) {
×
170
                        score.className = "loading";
×
171
                }
172

173
                return (
×
174
                        <LocationConsumer>
175
                                { location => {
176
                                        return (
×
177
                                                <RootContext.Consumer>
178
                                                        { ( { locationContext } ) => {
179
                                                                let upsellResults = [];
×
180
                                                                if ( this.props.shouldUpsell ) {
×
181
                                                                        upsellResults = this.getUpsellResults( location, locationContext );
×
182
                                                                }
NEW
183
                                                                const collapsibleId = `yoast-readability-analysis-collapsible-${ location }`;
×
184

185
                                                                if ( location === "sidebar" ) {
×
186
                                                                        return (
×
187
                                                                                <AIButtonFocusWrapper
188
                                                                                        results={ this.props.results }
189
                                                                                        fallbackElementId={ collapsibleId }
190
                                                                                >
191
                                                                                        <Collapsible
192
                                                                                                title={ __( "Readability analysis", "wordpress-seo" ) }
193
                                                                                                titleScreenReaderText={ score.screenReaderReadabilityText }
194
                                                                                                prefixIcon={ getIconForScore( score.className ) }
195
                                                                                                prefixIconCollapsed={ getIconForScore( score.className ) }
196
                                                                                                id={ collapsibleId }
197
                                                                                        >
198
                                                                                                { this.renderResults( upsellResults ) }
199
                                                                                        </Collapsible>
200
                                                                                </AIButtonFocusWrapper>
201
                                                                        );
202
                                                                }
203

204
                                                                if ( location === "metabox" ) {
×
205
                                                                        return (
×
206
                                                                                <AIButtonFocusWrapper
207
                                                                                        results={ this.props.results }
208
                                                                                        fallbackElementId={ collapsibleId }
209
                                                                                >
210
                                                                                        <ReadabilityResultsPortal target="wpseo-metabox-readability-root">
211
                                                                                                <ReadabilityResultsTabContainer
212
                                                                                                        id={ collapsibleId }
213
                                                                                                        tabIndex={ -1 }
214
                                                                                                >
215
                                                                                                        <ScoreIconPortal
216
                                                                                                                target="wpseo-readability-score-icon"
217
                                                                                                                scoreIndicator={ score.className }
218
                                                                                                        />
219
                                                                                                        { this.renderResults( upsellResults ) }
220
                                                                                                </ReadabilityResultsTabContainer>
221
                                                                                        </ReadabilityResultsPortal>
222
                                                                                </AIButtonFocusWrapper>
223
                                                                        );
224
                                                                }
225
                                                        } }
226
                                                </RootContext.Consumer>
227
                                        );
228
                                } }
229
                        </LocationConsumer>
230
                );
231
        }
232
}
233

234
ReadabilityAnalysis.propTypes = {
×
235
        results: PropTypes.array.isRequired,
236
        marksButtonStatus: PropTypes.string.isRequired,
237
        overallScore: PropTypes.number,
238
        shouldUpsell: PropTypes.bool,
239
        shouldUpsellHighlighting: PropTypes.bool,
240
        isAiFeatureEnabled: PropTypes.bool,
241
        isElementor: PropTypes.bool,
242
        isTerm: PropTypes.bool,
243
};
244

245
ReadabilityAnalysis.defaultProps = {
×
246
        overallScore: null,
247
        shouldUpsell: false,
248
        shouldUpsellHighlighting: false,
249
        isAiFeatureEnabled: false,
250
        isElementor: false,
251
        isTerm: false,
252
};
253

254
export default withSelect( select => {
255
        const {
256
                getReadabilityResults,
257
                getMarkButtonStatus,
258
                getIsElementorEditor,
259
                getIsAiFeatureEnabled,
260
                getIsTerm,
261
        } = select( "yoast-seo/editor" );
×
262

263
        return {
×
264
                ...getReadabilityResults(),
265
                marksButtonStatus: getMarkButtonStatus(),
266
                isElementor: getIsElementorEditor(),
267
                isAiFeatureEnabled: getIsAiFeatureEnabled(),
268
                isTerm: getIsTerm(),
269
        };
270
} )( ReadabilityAnalysis );
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc