Frontend Compatibility for Multiple Promoted Addon Groups (#13343)

This commit is contained in:
Christina Lin 2024-12-10 12:06:48 -05:00 коммит произвёл GitHub
Родитель 262e83a746
Коммит c55f660ab0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
10 изменённых файлов: 166 добавлений и 18 удалений

Просмотреть файл

@ -327,9 +327,10 @@ export const RECOMMENDED = 'recommended';
export const SPOTLIGHT = 'spotlight';
export const STRATEGIC = 'strategic';
// This array is sorted by "importance".
export const ALL_PROMOTED_CATEGORIES = [
LINE,
RECOMMENDED,
LINE,
SPOTLIGHT,
STRATEGIC,
];

Просмотреть файл

@ -11,6 +11,7 @@ import type {
UpdateRatingCountsAction,
} from 'amo/actions/reviews';
import {
makeInternalPromoted,
selectLocalizedContent,
selectCategoryObject,
} from 'amo/reducers/utils';
@ -236,7 +237,7 @@ export function createInternalAddon(
previews: apiAddon.previews
? createInternalPreviews(apiAddon.previews, lang)
: undefined,
promoted: apiAddon.promoted,
promoted: makeInternalPromoted(apiAddon.promoted),
ratings: apiAddon.ratings,
requires_payment: apiAddon.requires_payment,
review_url: apiAddon.review_url,

Просмотреть файл

@ -3,7 +3,10 @@ import invariant from 'invariant';
import { getAddonIconUrl } from 'amo/imageUtils';
import { SET_LANG } from 'amo/reducers/api';
import { selectLocalizedContent } from 'amo/reducers/utils';
import {
makeInternalPromoted,
selectLocalizedContent,
} from 'amo/reducers/utils';
import type { PromotedType } from 'amo/types/addons';
import type { LocalizedString } from 'amo/types/api';
@ -18,7 +21,7 @@ export type ExternalSuggestion = {|
icon_url: string,
id: number,
name: LocalizedString,
promoted: PromotedType | null,
promoted: Array<PromotedType> | PromotedType | null,
type: string,
url: string,
|};
@ -27,7 +30,7 @@ export type SuggestionType = {|
addonId: number,
iconUrl: string,
name: string,
promoted: PromotedType | null,
promoted: Array<PromotedType>,
type: string,
url: string,
|};
@ -108,7 +111,7 @@ export const createInternalSuggestion = (
addonId: externalSuggestion.id,
iconUrl: getAddonIconUrl(externalSuggestion),
name: selectLocalizedContent(externalSuggestion.name, lang),
promoted: externalSuggestion.promoted,
promoted: makeInternalPromoted(externalSuggestion.promoted),
type: externalSuggestion.type,
url: externalSuggestion.url,
};

Просмотреть файл

@ -3,7 +3,7 @@ import invariant from 'invariant';
import type { LocalizedString } from 'amo/types/api';
import type { CategoryEntry } from './categories';
import type { ExternalAddonType } from '../types/addons';
import type { ExternalAddonType, PromotedType } from '../types/addons';
export const selectLocalizedContent = (
field: LocalizedString,
@ -26,3 +26,12 @@ export const selectCategoryObject = (
): CategoryEntry => {
return apiAddon.categories;
};
export const makeInternalPromoted = (
promoted: Array<PromotedType> | PromotedType | null,
): Array<PromotedType> => {
if (!promoted) {
return [];
}
return Array.isArray(promoted) ? promoted : [promoted];
};

Просмотреть файл

@ -133,7 +133,7 @@ export type ExternalAddonType = {|
locale_disambiguation?: string,
name: LocalizedString,
previews?: Array<ExternalPreviewType>,
promoted: PromotedType | null,
promoted: Array<PromotedType> | PromotedType | null,
ratings: {|
average: number,
bayesian_average: number,
@ -174,6 +174,8 @@ export type AddonType = {|
summary: string | null,
support_email: string | null,
support_url: UrlWithOutgoing | null,
// normalized promoted categories,
promoted: Array<PromotedType>,
// Here are some custom properties for our internal representation.
currentVersionId: VersionIdType | null,
isMozillaSignedExtension: boolean,

Просмотреть файл

@ -10,6 +10,7 @@ import {
FATAL_INSTALL_ERROR,
FATAL_UNINSTALL_ERROR,
INSTALL_FAILED,
ALL_PROMOTED_CATEGORIES,
} from 'amo/constants';
import log from 'amo/logger';
import { getPreviewImage } from 'amo/imageUtils';
@ -121,15 +122,25 @@ export const getPromotedCategory = ({
clientApp: string,
forBadging?: boolean,
|}): PromotedCategoryType | null => {
let category = null;
if (addon && addon.promoted && addon.promoted.apps.includes(clientApp)) {
category = addon.promoted.category;
if (!addon?.promoted) {
return null;
}
// Special logic if we're using the category for badging.
if (forBadging && !BADGE_CATEGORIES.includes(category)) {
category = null;
}
const categories: Array<PromotedCategoryType> = addon.promoted
.filter((promoted) => {
if (!promoted.apps.includes(clientApp)) {
return false;
}
// Special logic if we're using the category for badging.
// We shouldn't add badges that are in BADGE_CATEGORIES.
return forBadging ? BADGE_CATEGORIES.includes(promoted.category) : true;
})
.map((promoted) => promoted.category)
.sort(
(a, b) =>
ALL_PROMOTED_CATEGORIES.indexOf(a) - ALL_PROMOTED_CATEGORIES.indexOf(b),
);
return category;
// Return only the 'most important' badge.
return categories.shift() || null;
};

Просмотреть файл

@ -27,6 +27,9 @@ import {
INCOMPATIBLE_UNSUPPORTED_PLATFORM,
INSTALLING,
RECOMMENDED,
LINE,
SPOTLIGHT,
STRATEGIC,
REVIEWER_TOOLS_VIEW,
SET_VIEW_CONTEXT,
STATIC_THEMES_REVIEW,
@ -2965,6 +2968,18 @@ describe(__filename, () => {
},
);
it('does not render the strategic or spotlight badges and correctly renders only the most important badge (RECOMMENDED)', () => {
const categories = [LINE, RECOMMENDED, STRATEGIC, SPOTLIGHT];
addon.promoted = categories.map((category) => ({
category,
apps: [clientApp],
}));
renderWithAddon();
const badges = screen.getAllByClassName('PromotedBadge');
expect(badges).toHaveLength(1);
expect(badges[0]).toHaveClass(`PromotedBadge--recommended`);
});
// See https://github.com/mozilla/addons-frontend/issues/8285.
it('does not pass an alt property to IconPromotedBadge', () => {
renderWithPromotedCategory();

Просмотреть файл

@ -87,7 +87,7 @@ describe(__filename, () => {
addonId: result.id,
iconUrl: result.icon_url,
name,
promoted,
promoted: [promoted],
url: result.url,
},
]);

Просмотреть файл

@ -1,4 +1,8 @@
import { selectLocalizedContent } from 'amo/reducers/utils';
import {
selectLocalizedContent,
makeInternalPromoted,
} from 'amo/reducers/utils';
import { CLIENT_APP_FIREFOX, RECOMMENDED } from 'amo/constants';
describe(__filename, () => {
describe('selectLocalizedContent', () => {
@ -33,4 +37,24 @@ describe(__filename, () => {
).toEqual(expected);
});
});
describe('makeInternalPromoted', () => {
it('returns the empty list if promoted is null', () => {
expect(makeInternalPromoted(null)).toEqual([]);
});
it('returns the empty list if promoted is empty', () => {
expect(makeInternalPromoted([])).toEqual([]);
});
it('returns promoted if promoted is a list', () => {
const promoted = [{ category: RECOMMENDED, apps: [CLIENT_APP_FIREFOX] }];
expect(makeInternalPromoted(promoted)).toEqual(promoted);
});
it('returns promoted in a list if promoted is an object', () => {
const promoted = { category: RECOMMENDED, apps: [CLIENT_APP_FIREFOX] };
expect(makeInternalPromoted(promoted)).toEqual([promoted]);
});
});
});

Просмотреть файл

@ -249,6 +249,88 @@ describe(__filename, () => {
).toEqual(category);
});
it('returns only the most important category if the addon is promoted in multiple categories for the specified app', () => {
const categories = [SPOTLIGHT, STRATEGIC, RECOMMENDED];
const promoted = categories.map((category) => ({
category,
apps: [CLIENT_APP_ANDROID],
}));
const addon = createInternalAddonWithLang({
...fakeAddon,
promoted,
});
const suggestion = createInternalSuggestionWithLang(
createFakeAutocompleteResult({
promoted,
}),
);
expect(
getPromotedCategory({
addon,
clientApp: CLIENT_APP_ANDROID,
}),
).toEqual(RECOMMENDED);
expect(
getPromotedCategory({
addon: suggestion,
clientApp: CLIENT_APP_ANDROID,
}),
).toEqual(RECOMMENDED);
});
it('returns null if the addon is promoted in multiple categories, but not for the specified app', () => {
const categories = [RECOMMENDED, SPOTLIGHT, STRATEGIC];
const promoted = categories.map((category) => ({
category,
apps: [CLIENT_APP_FIREFOX],
}));
const addon = createInternalAddonWithLang({
...fakeAddon,
promoted,
});
const suggestion = createInternalSuggestionWithLang(
createFakeAutocompleteResult({
promoted,
}),
);
expect(
getPromotedCategory({
addon,
clientApp: CLIENT_APP_ANDROID,
}),
).toEqual(null);
expect(
getPromotedCategory({
addon: suggestion,
clientApp: CLIENT_APP_ANDROID,
}),
).toEqual(null);
});
it('returns null if the addon is not promoted via empty list', () => {
const addon = createInternalAddonWithLang({
...fakeAddon,
promoted: [],
});
const suggestion = createInternalSuggestionWithLang(
createFakeAutocompleteResult({ promoted: [] }),
);
expect(
getPromotedCategory({ addon, clientApp: CLIENT_APP_ANDROID }),
).toEqual(null);
expect(
getPromotedCategory({
addon: suggestion,
clientApp: CLIENT_APP_ANDROID,
}),
).toEqual(null);
});
describe('forBadging === true', () => {
it.each([SPOTLIGHT, STRATEGIC])(
'returns null if the category is not one for badges, category: %s',