Skip to content

Commit 3d5f3d1

Browse files
authored
Use internal addon representation and themeData (#3320)
1 parent 4afeefb commit 3d5f3d1

File tree

13 files changed

+132
-84
lines changed

13 files changed

+132
-84
lines changed

src/amo/components/SearchResult/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ export class SearchResultBase extends React.Component {
4141

4242
// Fall-back to default icon if invalid icon url.
4343
const iconURL = getAddonIconUrl(addon);
44-
const themeURL = (addon && addon.theme_data &&
45-
isAllowedOrigin(addon.theme_data.previewURL)) ?
46-
addon.theme_data.previewURL : null;
44+
const themeURL = (addon && addon.themeData &&
45+
isAllowedOrigin(addon.themeData.previewURL)) ?
46+
addon.themeData.previewURL : null;
4747
const imageURL = isTheme ? themeURL : iconURL;
4848

4949
// Sets classes to handle fallback if theme preview is not available.

src/amo/reducers/addonsByAuthors.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* @flow */
2-
import type { AddonType } from 'core/types/addons';
2+
import type { AddonType, ExternalAddonType } from 'core/types/addons';
3+
import { createInternalAddon } from 'core/reducers/addons';
34

45

56
type State = {
@@ -67,7 +68,7 @@ export const fetchOtherAddonsByAuthors = (
6768

6869
type LoadOtherAddonsByAuthorsParams = {|
6970
slug: string,
70-
addons: Array<AddonType>,
71+
addons: Array<ExternalAddonType>,
7172
|};
7273

7374
type LoadOtherAddonsByAuthorsAction = {|
@@ -118,7 +119,8 @@ const reducer = (
118119
// This ensures we do not display the main add-on in the list of
119120
// "add-ons by these authors".
120121
.filter((addon) => addon.slug !== action.payload.slug)
121-
.slice(0, OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE),
122+
.slice(0, OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE)
123+
.map((addon) => createInternalAddon(addon)),
122124
},
123125
};
124126
default:

src/amo/reducers/landing.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
LANDING_GET,
33
LANDING_LOADED,
44
} from 'core/constants';
5+
import { createInternalAddon } from 'core/reducers/addons';
56

67

78
export const initialState = {
@@ -31,7 +32,7 @@ export default function landing(state = initialState, action) {
3132
newState[key] = {
3233
count: payload[key].result.count,
3334
results: payload[key].result.results.map((slug) => (
34-
payload[key].entities.addons[slug]
35+
createInternalAddon(payload[key].entities.addons[slug])
3536
)),
3637
};
3738
}

src/core/reducers/addons.js

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { oneLine } from 'common-tags';
44
import { ADDON_TYPE_THEME } from 'core/constants';
55
import type { ErrorHandlerType } from 'core/errorHandler';
66
import log from 'core/logger';
7-
import type { AddonType, ExternalAddonType } from 'core/types/addons';
7+
import type {
8+
AddonType,
9+
ExternalAddonType,
10+
ThemeData,
11+
} from 'core/types/addons';
812

913

1014
export const LOAD_ADDONS = 'LOAD_ADDONS';
@@ -80,6 +84,42 @@ export function removeUndefinedProps(object: Object): Object {
8084
return newObject;
8185
}
8286
87+
export function createInternalThemeData(
88+
apiAddon: ExternalAddonType
89+
): ThemeData | null {
90+
if (!apiAddon.theme_data) {
91+
return null;
92+
}
93+
94+
return {
95+
accentcolor: apiAddon.theme_data.accentcolor,
96+
author: apiAddon.theme_data.author,
97+
category: apiAddon.theme_data.category,
98+
99+
// TODO: Set this back to apiAddon.theme_data.description after
100+
// https://github.com/mozilla/addons-frontend/issues/1416 is fixed.
101+
// theme_data will contain `description: 'None'` when the description is
102+
// actually `null` and we don't want to set that on the addon itself so we
103+
// reset it in case it's been overwritten.
104+
//
105+
// See also https://github.com/mozilla/addons-server/issues/5650.
106+
description: apiAddon.description,
107+
108+
detailURL: apiAddon.theme_data.detailURL,
109+
footer: apiAddon.theme_data.footer,
110+
footerURL: apiAddon.theme_data.footerURL,
111+
header: apiAddon.theme_data.header,
112+
headerURL: apiAddon.theme_data.headerURL,
113+
iconURL: apiAddon.theme_data.iconURL,
114+
id: apiAddon.theme_data.id,
115+
name: apiAddon.theme_data.name,
116+
previewURL: apiAddon.theme_data.previewURL,
117+
textcolor: apiAddon.theme_data.textcolor,
118+
updateURL: apiAddon.theme_data.updateURL,
119+
version: apiAddon.theme_data.version,
120+
};
121+
}
122+
83123
export function createInternalAddon(
84124
apiAddon: ExternalAddonType
85125
): AddonType {
@@ -138,41 +178,21 @@ export function createInternalAddon(
138178
};
139179
140180
if (addon.type === ADDON_TYPE_THEME && apiAddon.theme_data) {
141-
// This merges theme_data into the addon.
142-
// TODO: Let's stop doing that because it's confusing. Lots of
143-
// deep button / install code will need to be fixed.
144-
addon = {
145-
...addon,
146-
...removeUndefinedProps({
147-
accentcolor: apiAddon.theme_data.accentcolor,
148-
author: apiAddon.theme_data.author,
149-
category: apiAddon.theme_data.category,
150-
151-
// TODO: Set this back to apiAddon.theme_data.description after
152-
// https://github.com/mozilla/addons-frontend/issues/1416
153-
// is fixed.
154-
// theme_data will contain `description: 'None'` when the
155-
// description
156-
// is actually `null` and we don't want to set that on the addon
157-
// itself so we reset it in case it's been overwritten.
158-
//
159-
// See also https://github.com/mozilla/addons-server/issues/5650.
160-
description: apiAddon.description,
161-
162-
detailURL: apiAddon.theme_data.detailURL,
163-
footer: apiAddon.theme_data.footer,
164-
footerURL: apiAddon.theme_data.footerURL,
165-
header: apiAddon.theme_data.header,
166-
headerURL: apiAddon.theme_data.headerURL,
167-
iconURL: apiAddon.theme_data.iconURL,
168-
id: apiAddon.theme_data.id,
169-
name: apiAddon.theme_data.name,
170-
previewURL: apiAddon.theme_data.previewURL,
171-
textcolor: apiAddon.theme_data.textcolor,
172-
updateURL: apiAddon.theme_data.updateURL,
173-
version: apiAddon.theme_data.version,
174-
}),
175-
};
181+
const themeData = createInternalThemeData(apiAddon);
182+
183+
if (themeData !== null) {
184+
// This merges theme_data into the addon.
185+
//
186+
// TODO: Let's stop doing that because it's confusing. Lots of deep
187+
// button/install code will need to be fixed.
188+
//
189+
// Use addon.themeData[themeProp] instead of addon[themeProp].
190+
addon = {
191+
...addon,
192+
...removeUndefinedProps(themeData),
193+
themeData,
194+
};
195+
}
176196
}
177197
178198
if (apiAddon.current_version && apiAddon.current_version.files.length > 0) {

src/core/reducers/search.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
SEARCH_STARTED,
33
SEARCH_LOADED,
44
} from 'core/constants';
5+
import { createInternalAddon } from 'core/reducers/addons';
56

67
export const initialState = {
78
count: 0,
@@ -26,7 +27,7 @@ export default function search(state = initialState, action) {
2627
count: payload.result.count,
2728
loading: false,
2829
results: payload.result.results.map((slug) => (
29-
payload.entities.addons[slug]
30+
createInternalAddon(payload.entities.addons[slug])
3031
)),
3132
};
3233
default:

src/core/types/addons.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export type AddonAuthorType = {|
5151
username: string,
5252
|};
5353

54-
type ThemeData = {|
54+
export type ThemeData = {|
5555
accentcolor?: string,
5656
author?: string,
5757
category?: string,
@@ -141,4 +141,5 @@ export type AddonType = {
141141
windows: ?string,
142142
},
143143
isRestartRequired: boolean,
144+
themeData?: ThemeData,
144145
};

tests/unit/amo/components/TestAddon.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,9 @@ describe(__filename, () => {
11041104
const root = renderMoreAddons({ addon, addonsByAuthors });
11051105

11061106
expect(root).not.toHaveClassName('.AddonDescription-more-addons--theme');
1107-
expect(root).toHaveProp('addons', addonsByAuthors);
1107+
expect(root).toHaveProp('addons', addonsByAuthors.map((addonByAuthor) => (
1108+
createInternalAddon(addonByAuthor)
1109+
)));
11081110
});
11091111

11101112
it('indicates when other add-ons are themes', () => {
@@ -1118,7 +1120,9 @@ describe(__filename, () => {
11181120
const root = renderMoreAddons({ addon, addonsByAuthors });
11191121

11201122
expect(root).toHaveClassName('.AddonDescription-more-addons--theme');
1121-
expect(root).toHaveProp('addons', addonsByAuthors);
1123+
expect(root).toHaveProp('addons', addonsByAuthors.map((addonByAuthor) => (
1124+
createInternalAddon(addonByAuthor)
1125+
)));
11221126
});
11231127

11241128
it('adds a CSS class to the main component when there are add-ons', () => {

tests/unit/amo/components/TestSearchResult.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { shallow } from 'enzyme';
22
import React from 'react';
33

44
import { SearchResultBase } from 'amo/components/SearchResult';
5+
import { createInternalAddon } from 'core/reducers/addons';
56
import { fakeAddon } from 'tests/unit/amo/helpers';
67
import { getFakeI18nInst } from 'tests/unit/helpers';
78
import { ADDON_TYPE_THEME } from 'core/constants';
@@ -10,7 +11,7 @@ import Rating from 'ui/components/Rating';
1011

1112

1213
describe(__filename, () => {
13-
const baseAddon = {
14+
const baseAddon = createInternalAddon({
1415
...fakeAddon,
1516
authors: [
1617
{ name: 'A funky déveloper' },
@@ -19,7 +20,7 @@ describe(__filename, () => {
1920
average_daily_users: 5253,
2021
name: 'A search result',
2122
slug: 'a-search-result',
22-
};
23+
});
2324

2425
function render({ addon = baseAddon, lang = 'en-GB', ...props } = {}) {
2526
return shallow(
@@ -46,7 +47,9 @@ describe(__filename, () => {
4647
});
4748

4849
it('ignores an empty author list', () => {
49-
const root = render({ addon: { ...fakeAddon, authors: undefined } });
50+
const root = render({
51+
addon: createInternalAddon({ ...fakeAddon, authors: undefined }),
52+
});
5053

5154
expect(root).not.toHaveClassName('SearchResult-author');
5255
});
@@ -73,7 +76,12 @@ describe(__filename, () => {
7376
});
7477

7578
it('renders the user count as singular', () => {
76-
const root = render({ addon: { ...fakeAddon, average_daily_users: 1 } });
79+
const root = render({
80+
addon: createInternalAddon({
81+
...fakeAddon,
82+
average_daily_users: 1,
83+
}),
84+
});
7785

7886
expect(root.find('.SearchResult-users')).toIncludeText('1 user');
7987
});
@@ -104,7 +112,7 @@ describe(__filename, () => {
104112
});
105113

106114
it('displays a placeholder if the icon is malformed', () => {
107-
const addon = { ...fakeAddon, icon_url: 'whatevs' };
115+
const addon = createInternalAddon({ ...fakeAddon, icon_url: 'whatevs' });
108116
const root = render({ addon });
109117

110118
// image `require` calls in jest return the filename
@@ -113,20 +121,23 @@ describe(__filename, () => {
113121
});
114122

115123
it('adds a theme-specific class', () => {
116-
const addon = {
124+
const addon = createInternalAddon({
117125
...fakeAddon,
118126
type: ADDON_TYPE_THEME,
119127
theme_data: {
120128
previewURL: 'https://addons.cdn.mozilla.net/user-media/addons/334902/preview_large.jpg?1313374873',
121129
},
122-
};
130+
});
123131
const root = render({ addon });
124132

125133
expect(root).toHaveClassName('SearchResult--theme');
126134
});
127135

128136
it('displays a message if the theme preview image is unavailable', () => {
129-
const addon = { ...fakeAddon, type: ADDON_TYPE_THEME };
137+
const addon = createInternalAddon({
138+
...fakeAddon,
139+
type: ADDON_TYPE_THEME,
140+
});
130141
const root = render({ addon });
131142

132143
expect(root.find('.SearchResult-notheme'))

tests/unit/amo/reducers/testLanding.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { getLanding } from 'amo/actions/landing';
22
import landing, { initialState } from 'amo/reducers/landing';
33
import { ADDON_TYPE_THEME } from 'core/constants';
4+
import { createInternalAddon } from 'core/reducers/addons';
45
import { fakeAddon } from 'tests/unit/amo/helpers';
56

67

7-
describe('landing reducer', () => {
8+
describe(__filename, () => {
89
it('defaults to not loading', () => {
910
const { loading } = landing(undefined, { type: 'unrelated' });
1011

@@ -84,9 +85,9 @@ describe('landing reducer', () => {
8485
it('sets the results', () => {
8586
const entities = {
8687
addons: {
87-
bar: { slug: 'bar' },
88-
foo: { slug: 'foo' },
89-
food: { slug: 'food' },
88+
bar: { ...fakeAddon, slug: 'bar' },
89+
foo: { ...fakeAddon, slug: 'foo' },
90+
food: { ...fakeAddon, slug: 'food' },
9091
},
9192
};
9293
const state = landing(initialState, {
@@ -103,7 +104,10 @@ describe('landing reducer', () => {
103104
});
104105
expect(state.featured.count).toEqual(2);
105106
expect(state.featured.results)
106-
.toEqual([{ slug: 'foo' }, { slug: 'food' }]);
107+
.toEqual([
108+
createInternalAddon({ ...fakeAddon, slug: 'foo' }),
109+
createInternalAddon({ ...fakeAddon, slug: 'food' }),
110+
]);
107111
expect(state.highlyRated).toEqual({ count: 0, results: [] });
108112
expect(state.popular).toEqual({ count: 0, results: [] });
109113
expect(state.resultsLoaded).toEqual(true);
@@ -112,9 +116,9 @@ describe('landing reducer', () => {
112116
it('does not set null keys', () => {
113117
const entities = {
114118
addons: {
115-
bar: { slug: 'bar' },
116-
foo: { slug: 'foo' },
117-
food: { slug: 'food' },
119+
bar: { ...fakeAddon, slug: 'bar' },
120+
foo: { ...fakeAddon, slug: 'foo' },
121+
food: { ...fakeAddon, slug: 'food' },
118122
},
119123
};
120124
const { highlyRated } = landing({

tests/unit/amo/reducers/test_addonsByAuthors.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import reducer, {
55
initialState,
66
loadOtherAddonsByAuthors,
77
} from 'amo/reducers/addonsByAuthors';
8+
import { createInternalAddon } from 'core/reducers/addons';
89
import { fakeAddon } from 'tests/unit/amo/helpers';
910

1011

@@ -42,7 +43,7 @@ describe(__filename, () => {
4243
addons: [fakeAddon],
4344
}));
4445
expect(state.byAddonSlug).toEqual({
45-
'addon-slug': [fakeAddon],
46+
'addon-slug': [createInternalAddon(fakeAddon)],
4647
});
4748
});
4849

@@ -75,7 +76,7 @@ describe(__filename, () => {
7576
slug,
7677
}));
7778
expect(previousState.byAddonSlug)
78-
.toEqual({ 'addon-slug': [fakeAddon] });
79+
.toEqual({ 'addon-slug': [createInternalAddon(fakeAddon)] });
7980

8081
const state = reducer(previousState, fetchOtherAddonsByAuthors({
8182
authors: ['author1'],

0 commit comments

Comments
 (0)