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

Yoast / wordpress-seo / 417791c2a2fb7bd7eca7bf02b29bbd4b3f722d5d

08 Feb 2025 10:09PM UTC coverage: 42.352%. First build
417791c2a2fb7bd7eca7bf02b29bbd4b3f722d5d

Pull #22008

github

web-flow
Merge 8e03848bf into 0a28ab53b
Pull Request #22008: Create frontend providers and widget factory

1849 of 7548 branches covered (24.5%)

Branch coverage included in aggregate %.

104 of 129 new or added lines in 13 files covered. (80.62%)

20848 of 46043 relevant lines covered (45.28%)

4.27 hits per line

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

97.37
/packages/js/src/dashboard/scores/components/scores.js
1
import { createInterpolateElement, useCallback, useEffect, useState } from "@wordpress/element";
2
import { __, sprintf } from "@wordpress/i18n";
3
import { Alert, Link } from "@yoast/ui-library";
4
import { useRemoteData } from "../../services/use-remote-data";
5
import { SCORE_DESCRIPTIONS } from "../score-meta";
6
import { ContentTypeFilter } from "./content-type-filter";
7
import { ScoreContent } from "./score-content";
8
import { TermFilter } from "./term-filter";
9

10
/**
11
 * @type {import("../index").ContentType} ContentType
12
 * @type {import("../index").Taxonomy} Taxonomy
13
 * @type {import("../index").Term} Term
14
 * @type {import("../index").AnalysisType} AnalysisType
15
 */
16

17
/**
18
 * @param {string} message The message with placeholders.
19
 * @param {JSX.Element} link The link.
20
 * @returns {JSX.Element|string} The message.
21
 */
22
const createLinkMessage = ( message, link ) => {
4✔
23
        try {
4✔
24
                return createInterpolateElement( sprintf( message, "<link>", "</link>" ), { link } );
4✔
25
        } catch ( e ) {
NEW
26
                return sprintf( message, "", "" );
×
27
        }
28
};
29

30
/**
31
 * @param {Error?} [error] The error.
32
 * @param {string} supportLink The support link.
33
 * @returns {JSX.Element} The element.
34
 */
35
const ErrorAlert = ( { error, supportLink } ) => {
4✔
36
        if ( ! error ) {
66✔
37
                return null;
62✔
38
        }
39

40
        // Added dummy space as content to prevent children prop warnings in the console.
41
        const link = <Link variant="error" href={ supportLink }> </Link>;
4✔
42

43
        return (
4✔
44
                <Alert variant="error">
45
                        { error?.name === "TimeoutError"
2✔
46
                                ? createLinkMessage(
47
                                        /* translators: %1$s and %2$s expand to an opening/closing tag for a link to the support page. */
48
                                        __( "A timeout occurred, possibly due to a large number of posts or terms. In case you need further help, please take a look at our %1$sSupport page%2$s.", "wordpress-seo" ),
49
                                        link
50
                                )
51
                                : createLinkMessage(
52
                                        /* translators: %1$s and %2$s expand to an opening/closing tag for a link to the support page. */
53
                                        __( "Something went wrong. In case you need further help, please take a look at our %1$sSupport page%2$s.", "wordpress-seo" ),
54
                                        link
55
                                )
56
                        }
57
                </Alert>
58
        );
59
};
60

61
/**
62
 * @param {ContentType?} [contentType] The selected content type.
63
 * @param {Term?} [term] The selected term.
64
 * @returns {{contentType: string, taxonomy?: string, term?: string}} The score query parameters.
65
 */
66
const getScoreQueryParams = ( contentType, term ) => { // eslint-disable-line complexity
4✔
67
        const params = {
30✔
68
                contentType: contentType?.name,
69
        };
70
        if ( contentType?.taxonomy?.name && term?.name ) {
30✔
71
                params.taxonomy = contentType.taxonomy.name;
2✔
72
                params.term = term.name;
2✔
73
        }
74

75
        return params;
30✔
76
};
77

78
/**
79
 * @param {?{scores: Score[]}} [data] The data.
80
 * @returns {?Score[]} scores The scores.
81
 */
82
const prepareScoreData = ( data ) => data?.scores;
26✔
83

84
/**
85
 * @param {AnalysisType} analysisType The analysis type. Either "seo" or "readability".
86
 * @param {ContentType[]} contentTypes The content types. May not be empty.
87
 * @param {import("../services/data-provider")} dataProvider The data provider.
88
 * @param {import("../services/remote-data-provider")} remoteDataProvider The remote data provider.
89
 * @returns {JSX.Element} The element.
90
 */
91
export const Scores = ( { analysisType, contentTypes, dataProvider, remoteDataProvider } ) => { // eslint-disable-line complexity
4✔
92
        const [ selectedContentType, setSelectedContentType ] = useState( contentTypes[ 0 ] );
90✔
93
        /** @type {[Term?, function(Term?)]} */
94
        const [ selectedTerm, setSelectedTerm ] = useState();
90✔
95

96
        const getScores = useCallback( ( options ) => remoteDataProvider.fetchJson(
90✔
97
                dataProvider.getEndpoint( analysisType + "Scores" ),
98
                getScoreQueryParams( selectedContentType, selectedTerm ),
99
                options
100
        ), [ dataProvider, analysisType, selectedContentType, selectedTerm ] );
101

102
        const { data: scores, error, isPending } = useRemoteData( getScores, prepareScoreData );
90✔
103

104
        useEffect( () => {
90✔
105
                // Reset the selected term when the selected content type changes.
106
                setSelectedTerm( undefined ); // eslint-disable-line no-undefined
28✔
107
        }, [ selectedContentType?.name ] );
108

109
        return (
90✔
110
                <>
111
                        <div className="yst-grid yst-grid-cols-1 @md:yst-grid-cols-2 yst-gap-6 yst-mt-4">
112
                                <ContentTypeFilter
113
                                        idSuffix={ analysisType }
114
                                        contentTypes={ contentTypes }
115
                                        selected={ selectedContentType }
116
                                        onChange={ setSelectedContentType }
117
                                />
118
                                { selectedContentType.taxonomy && selectedContentType.taxonomy?.links?.search &&
129✔
119
                                        <TermFilter
120
                                                idSuffix={ analysisType }
121
                                                taxonomy={ selectedContentType.taxonomy }
122
                                                selected={ selectedTerm }
123
                                                onChange={ setSelectedTerm }
124
                                        />
125
                                }
126
                        </div>
127
                        <div className="yst-mt-6">
128
                                <ErrorAlert error={ error } supportLink={ dataProvider.getLink( "errorSupport" ) } />
129
                                { ! error && (
88✔
130
                                        <ScoreContent
131
                                                scores={ scores }
132
                                                isLoading={ isPending }
133
                                                descriptions={ SCORE_DESCRIPTIONS[ analysisType ] }
134
                                                idSuffix={ analysisType }
135
                                        />
136
                                ) }
137
                        </div>
138
                </>
139
        );
140
};
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

© 2025 Coveralls, Inc