Skip to content

Commit 195e512

Browse files
authored
Adjust rating card layout and show rating breakdown (#13661)
* Adjust rating card layout and show rating breakdown * simplify header code in Addon card; localize "Click to Rate" * add a consistent gap around rating review buttons/ui * add flex-wrap, to account for smaller RatingManager widths
1 parent 63db836 commit 195e512

File tree

7 files changed

+84
-25
lines changed

7 files changed

+84
-25
lines changed

src/amo/components/AddonReviewCard/styles.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166

167167
.AddonReviewCard-confirmDeleteDialog {
168168
margin-top: 12px;
169+
margin-bottom: 6px;
169170
text-align: center;
170171
width: 100%;
171172

@@ -216,6 +217,7 @@
216217
background-color: transparentize($blue-50, 0.95);
217218
border-radius: $border-radius-default;
218219
padding: 12px;
220+
margin-bottom: 6px;
219221

220222
@include respond-to(medium) {
221223
padding: 12px 24px;

src/amo/components/Rating/styles.scss

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ $icon-small: 13px;
2121
// The width of large rating stars are controlled by the container.
2222
&.Rating--large {
2323
grid-column-gap: 6px;
24-
max-width: 300px;
25-
min-height: 64px;
24+
max-width: 250px;
25+
min-height: 32px;
2626
width: 100%;
2727
}
2828

2929
@include respond-to(extraExtraLarge) {
3030
&.Rating--large {
3131
grid-column-gap: 12px;
32-
max-width: none;
3332
}
3433
}
3534
}

src/amo/components/RatingManager/index.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import AddonReviewCard from 'amo/components/AddonReviewCard';
1717
import AddonReviewManagerRating from 'amo/components/AddonReviewManagerRating';
1818
import RatingManagerNotice from 'amo/components/RatingManagerNotice';
19+
import RatingsByStar from 'amo/components/RatingsByStar';
1920
import { selectLatestUserReview } from 'amo/reducers/reviews';
2021
import AuthenticateButton from 'amo/components/AuthenticateButton';
2122
import {
@@ -189,14 +190,14 @@ export class RatingManagerBase extends React.Component<InternalProps> {
189190
'Are you sure you want to delete your rating of %(addonName)s?',
190191
);
191192
}
192-
} else {
193-
prompt = i18n.gettext('How are you enjoying %(addonName)s?');
194193
}
195194

196-
const promptHTML = sanitizeHTML(
197-
i18n.sprintf(prompt, { addonName: `<b>${addon.name}</b>` }),
198-
['b'],
199-
);
195+
const promptHTML = prompt
196+
? sanitizeHTML(
197+
i18n.sprintf(prompt, { addonName: `<b>${addon.name}</b>` }),
198+
['b'],
199+
)
200+
: null;
200201

201202
return (
202203
<form action="">
@@ -209,6 +210,7 @@ export class RatingManagerBase extends React.Component<InternalProps> {
209210
{/* eslint-enable react/no-danger */}
210211
<div className="RatingManager-ratingControl">
211212
{!this.isSignedIn() ? this.renderLogInToRate() : null}
213+
{i18n.gettext('Click to rate:')}
212214
{userReview && onDeleteScreen ? (
213215
<AddonReviewManagerRating
214216
className="RatingManager-AddonReviewManagerRating"
@@ -267,6 +269,7 @@ export class RatingManagerBase extends React.Component<InternalProps> {
267269
slim
268270
/>
269271
)}
272+
<RatingsByStar addon={addon} />
270273
</div>
271274
);
272275
}

src/amo/components/RatingManager/styles.scss

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
.RatingManager-ratingControl {
44
align-items: center;
55
display: flex;
6-
justify-content: center;
6+
flex-wrap: wrap;
7+
gap: 6px;
78
position: relative;
89
}
910

@@ -19,6 +20,10 @@
1920
}
2021

2122
.RatingManager {
23+
display: flex;
24+
flex-direction: column;
25+
gap: 12px;
26+
2227
fieldset {
2328
// Prevent content overflow.
2429
min-width: 0;
@@ -32,10 +37,6 @@
3237
}
3338
}
3439

35-
.RatingManager-UserRating {
36-
margin: 12px;
37-
}
38-
3940
.RatingManager-savedRating-withReview {
4041
margin-bottom: 12px;
4142
}

src/amo/pages/Addon/index.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
77

88
import { setViewContext } from 'amo/actions/viewContext';
99
import AddAddonToCollection from 'amo/components/AddAddonToCollection';
10-
import AddonBadges from 'amo/components/AddonBadges';
10+
import AddonBadges, { roundToOneDigit } from 'amo/components/AddonBadges';
1111
import AddonCompatibilityError from 'amo/components/AddonCompatibilityError';
1212
import AddonHead from 'amo/components/AddonHead';
1313
import AddonInstallError from 'amo/components/AddonInstallError';
@@ -244,17 +244,35 @@ export class AddonBase extends React.Component {
244244
content = i18n.gettext('No reviews yet');
245245
}
246246

247+
let header;
248+
if (addon && addon.ratings && addon.ratings.count !== undefined) {
249+
const roundedAverage = roundToOneDigit(addon.ratings.average);
250+
const ratingCount = addon.ratings.count;
251+
header = i18n.sprintf(
252+
// eslint-disable-next-line max-len
253+
// L10n: ratingAverage is a number rounded to one digit, such as 4.5 in English or ٤٫٧ in Arabic.
254+
i18n.ngettext(
255+
'Rated %(ratingAverage)s by 1 reviewer',
256+
'Rated %(ratingAverage)s by %(ratingCount)s reviewers',
257+
ratingCount,
258+
),
259+
{
260+
ratingAverage: i18n.formatNumber(roundedAverage),
261+
ratingCount: i18n.formatNumber(ratingCount),
262+
},
263+
);
264+
} else {
265+
header = <LoadingText minWidth={30} />;
266+
}
267+
247268
const props = {
248269
[footerPropName]: (
249270
<div className="Addon-read-reviews-footer">{content}</div>
250271
),
251272
};
273+
252274
return (
253-
<Card
254-
header={i18n.gettext('Rate your experience')}
255-
className="Addon-overall-rating"
256-
{...props}
257-
>
275+
<Card header={header} className="Addon-overall-rating" {...props}>
258276
{ratingManager}
259277
</Card>
260278
);

tests/unit/amo/components/TestRatingManager.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe(__filename, () => {
110110
return { addon, review };
111111
};
112112

113-
it('prompts you to rate the add-on by name', () => {
113+
it('doesnt show any prompt when not deleting', () => {
114114
const name = 'Some Add-on';
115115
render({
116116
addon: createInternalAddonWithLang({
@@ -119,9 +119,7 @@ describe(__filename, () => {
119119
}),
120120
});
121121

122-
expect(
123-
screen.getByTextAcrossTags(`How are you enjoying ${name}?`),
124-
).toBeInTheDocument();
122+
expect(screen.queryByText(`of ${name}?`)).not.toBeInTheDocument();
125123
});
126124

127125
it('dispatches fetchLatestUserReview on construction', () => {
@@ -534,4 +532,15 @@ describe(__filename, () => {
534532
expect(screen.getAllByTitle('Rated 3 out of 5')).toHaveLength(6);
535533
});
536534
});
535+
536+
it('renders RatingsByStar with an add-on', () => {
537+
const addon = fakeAddon;
538+
render({ addon });
539+
540+
// Do a sanity check to make sure the right add-on was used.
541+
expect(screen.getByTitle('There are no five-star reviews')).toHaveAttribute(
542+
'href',
543+
`/en-US/android/addon/${addon.slug}/reviews/?score=5`,
544+
);
545+
});
537546
});

tests/unit/amo/pages/TestAddon.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ describe(__filename, () => {
386386
it('renders without an add-on', () => {
387387
render();
388388

389-
expect(screen.getAllByRole('alert')).toHaveLength(6);
389+
expect(screen.getAllByRole('alert')).toHaveLength(7);
390390
});
391391

392392
it('renders without a version', () => {
@@ -1021,6 +1021,33 @@ describe(__filename, () => {
10211021
expect(content).toHaveTextContent('100 Users');
10221022
});
10231023

1024+
describe('ratings count header', () => {
1025+
const renderWithRatings = (average, count) => {
1026+
addon.ratings = { ...fakeAddon.ratings, count, average };
1027+
renderWithAddon();
1028+
};
1029+
1030+
it('mentions a single reviewer when there is only one rating', () => {
1031+
renderWithRatings(3, 1);
1032+
1033+
expect(screen.getByText('Rated 3 by 1 reviewer')).toBeInTheDocument();
1034+
});
1035+
1036+
it('mentions a multiple reviewers when there are many ratings', () => {
1037+
renderWithRatings(2.4, 5);
1038+
1039+
expect(screen.getByText('Rated 2.4 by 5 reviewers')).toBeInTheDocument();
1040+
});
1041+
1042+
it('localizes the review count', () => {
1043+
renderWithRatings(2.4, 10000);
1044+
1045+
expect(
1046+
screen.getByText('Rated 2.4 by 10,000 reviewers'),
1047+
).toBeInTheDocument();
1048+
});
1049+
});
1050+
10241051
describe('read reviews footer', () => {
10251052
const renderWithRatings = (count) => {
10261053
addon.ratings = { ...fakeAddon.ratings, count };

0 commit comments

Comments
 (0)