Skip to content

Commit

Permalink
Use internal addon representation and themeData (#3320)
Browse files Browse the repository at this point in the history
  • Loading branch information
willdurand committed Oct 3, 2017
1 parent 4afeefb commit 3d5f3d1
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 84 deletions.
6 changes: 3 additions & 3 deletions src/amo/components/SearchResult/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export class SearchResultBase extends React.Component {

// Fall-back to default icon if invalid icon url.
const iconURL = getAddonIconUrl(addon);
const themeURL = (addon && addon.theme_data &&
isAllowedOrigin(addon.theme_data.previewURL)) ?
addon.theme_data.previewURL : null;
const themeURL = (addon && addon.themeData &&
isAllowedOrigin(addon.themeData.previewURL)) ?
addon.themeData.previewURL : null;
const imageURL = isTheme ? themeURL : iconURL;

// Sets classes to handle fallback if theme preview is not available.
Expand Down
8 changes: 5 additions & 3 deletions src/amo/reducers/addonsByAuthors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* @flow */
import type { AddonType } from 'core/types/addons';
import type { AddonType, ExternalAddonType } from 'core/types/addons';
import { createInternalAddon } from 'core/reducers/addons';


type State = {
Expand Down Expand Up @@ -67,7 +68,7 @@ export const fetchOtherAddonsByAuthors = (

type LoadOtherAddonsByAuthorsParams = {|
slug: string,
addons: Array<AddonType>,
addons: Array<ExternalAddonType>,
|};

type LoadOtherAddonsByAuthorsAction = {|
Expand Down Expand Up @@ -118,7 +119,8 @@ const reducer = (
// This ensures we do not display the main add-on in the list of
// "add-ons by these authors".
.filter((addon) => addon.slug !== action.payload.slug)
.slice(0, OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE),
.slice(0, OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE)
.map((addon) => createInternalAddon(addon)),
},
};
default:
Expand Down
3 changes: 2 additions & 1 deletion src/amo/reducers/landing.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
LANDING_GET,
LANDING_LOADED,
} from 'core/constants';
import { createInternalAddon } from 'core/reducers/addons';


export const initialState = {
Expand Down Expand Up @@ -31,7 +32,7 @@ export default function landing(state = initialState, action) {
newState[key] = {
count: payload[key].result.count,
results: payload[key].result.results.map((slug) => (
payload[key].entities.addons[slug]
createInternalAddon(payload[key].entities.addons[slug])
)),
};
}
Expand Down
92 changes: 56 additions & 36 deletions src/core/reducers/addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { oneLine } from 'common-tags';
import { ADDON_TYPE_THEME } from 'core/constants';
import type { ErrorHandlerType } from 'core/errorHandler';
import log from 'core/logger';
import type { AddonType, ExternalAddonType } from 'core/types/addons';
import type {
AddonType,
ExternalAddonType,
ThemeData,
} from 'core/types/addons';


export const LOAD_ADDONS = 'LOAD_ADDONS';
Expand Down Expand Up @@ -80,6 +84,42 @@ export function removeUndefinedProps(object: Object): Object {
return newObject;
}

export function createInternalThemeData(
apiAddon: ExternalAddonType
): ThemeData | null {
if (!apiAddon.theme_data) {
return null;
}

return {
accentcolor: apiAddon.theme_data.accentcolor,
author: apiAddon.theme_data.author,
category: apiAddon.theme_data.category,

// TODO: Set this back to apiAddon.theme_data.description after
// https://github.com/mozilla/addons-frontend/issues/1416 is fixed.
// theme_data will contain `description: 'None'` when the description is
// actually `null` and we don't want to set that on the addon itself so we
// reset it in case it's been overwritten.
//
// See also https://github.com/mozilla/addons-server/issues/5650.
description: apiAddon.description,

detailURL: apiAddon.theme_data.detailURL,
footer: apiAddon.theme_data.footer,
footerURL: apiAddon.theme_data.footerURL,
header: apiAddon.theme_data.header,
headerURL: apiAddon.theme_data.headerURL,
iconURL: apiAddon.theme_data.iconURL,
id: apiAddon.theme_data.id,
name: apiAddon.theme_data.name,
previewURL: apiAddon.theme_data.previewURL,
textcolor: apiAddon.theme_data.textcolor,
updateURL: apiAddon.theme_data.updateURL,
version: apiAddon.theme_data.version,
};
}

export function createInternalAddon(
apiAddon: ExternalAddonType
): AddonType {
Expand Down Expand Up @@ -138,41 +178,21 @@ export function createInternalAddon(
};

if (addon.type === ADDON_TYPE_THEME && apiAddon.theme_data) {
// This merges theme_data into the addon.
// TODO: Let's stop doing that because it's confusing. Lots of
// deep button / install code will need to be fixed.
addon = {
...addon,
...removeUndefinedProps({
accentcolor: apiAddon.theme_data.accentcolor,
author: apiAddon.theme_data.author,
category: apiAddon.theme_data.category,

// TODO: Set this back to apiAddon.theme_data.description after
// https://github.com/mozilla/addons-frontend/issues/1416
// is fixed.
// theme_data will contain `description: 'None'` when the
// description
// is actually `null` and we don't want to set that on the addon
// itself so we reset it in case it's been overwritten.
//
// See also https://github.com/mozilla/addons-server/issues/5650.
description: apiAddon.description,

detailURL: apiAddon.theme_data.detailURL,
footer: apiAddon.theme_data.footer,
footerURL: apiAddon.theme_data.footerURL,
header: apiAddon.theme_data.header,
headerURL: apiAddon.theme_data.headerURL,
iconURL: apiAddon.theme_data.iconURL,
id: apiAddon.theme_data.id,
name: apiAddon.theme_data.name,
previewURL: apiAddon.theme_data.previewURL,
textcolor: apiAddon.theme_data.textcolor,
updateURL: apiAddon.theme_data.updateURL,
version: apiAddon.theme_data.version,
}),
};
const themeData = createInternalThemeData(apiAddon);

if (themeData !== null) {
// This merges theme_data into the addon.
//
// TODO: Let's stop doing that because it's confusing. Lots of deep
// button/install code will need to be fixed.
//
// Use addon.themeData[themeProp] instead of addon[themeProp].
addon = {
...addon,
...removeUndefinedProps(themeData),
themeData,
};
}
}

if (apiAddon.current_version && apiAddon.current_version.files.length > 0) {
Expand Down
3 changes: 2 additions & 1 deletion src/core/reducers/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
SEARCH_STARTED,
SEARCH_LOADED,
} from 'core/constants';
import { createInternalAddon } from 'core/reducers/addons';

export const initialState = {
count: 0,
Expand All @@ -26,7 +27,7 @@ export default function search(state = initialState, action) {
count: payload.result.count,
loading: false,
results: payload.result.results.map((slug) => (
payload.entities.addons[slug]
createInternalAddon(payload.entities.addons[slug])
)),
};
default:
Expand Down
3 changes: 2 additions & 1 deletion src/core/types/addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export type AddonAuthorType = {|
username: string,
|};

type ThemeData = {|
export type ThemeData = {|
accentcolor?: string,
author?: string,
category?: string,
Expand Down Expand Up @@ -141,4 +141,5 @@ export type AddonType = {
windows: ?string,
},
isRestartRequired: boolean,
themeData?: ThemeData,
};
8 changes: 6 additions & 2 deletions tests/unit/amo/components/TestAddon.js
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,9 @@ describe(__filename, () => {
const root = renderMoreAddons({ addon, addonsByAuthors });

expect(root).not.toHaveClassName('.AddonDescription-more-addons--theme');
expect(root).toHaveProp('addons', addonsByAuthors);
expect(root).toHaveProp('addons', addonsByAuthors.map((addonByAuthor) => (
createInternalAddon(addonByAuthor)
)));
});

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

expect(root).toHaveClassName('.AddonDescription-more-addons--theme');
expect(root).toHaveProp('addons', addonsByAuthors);
expect(root).toHaveProp('addons', addonsByAuthors.map((addonByAuthor) => (
createInternalAddon(addonByAuthor)
)));
});

it('adds a CSS class to the main component when there are add-ons', () => {
Expand Down
27 changes: 19 additions & 8 deletions tests/unit/amo/components/TestSearchResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { shallow } from 'enzyme';
import React from 'react';

import { SearchResultBase } from 'amo/components/SearchResult';
import { createInternalAddon } from 'core/reducers/addons';
import { fakeAddon } from 'tests/unit/amo/helpers';
import { getFakeI18nInst } from 'tests/unit/helpers';
import { ADDON_TYPE_THEME } from 'core/constants';
Expand All @@ -10,7 +11,7 @@ import Rating from 'ui/components/Rating';


describe(__filename, () => {
const baseAddon = {
const baseAddon = createInternalAddon({
...fakeAddon,
authors: [
{ name: 'A funky déveloper' },
Expand All @@ -19,7 +20,7 @@ describe(__filename, () => {
average_daily_users: 5253,
name: 'A search result',
slug: 'a-search-result',
};
});

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

it('ignores an empty author list', () => {
const root = render({ addon: { ...fakeAddon, authors: undefined } });
const root = render({
addon: createInternalAddon({ ...fakeAddon, authors: undefined }),
});

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

it('renders the user count as singular', () => {
const root = render({ addon: { ...fakeAddon, average_daily_users: 1 } });
const root = render({
addon: createInternalAddon({
...fakeAddon,
average_daily_users: 1,
}),
});

expect(root.find('.SearchResult-users')).toIncludeText('1 user');
});
Expand Down Expand Up @@ -104,7 +112,7 @@ describe(__filename, () => {
});

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

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

it('adds a theme-specific class', () => {
const addon = {
const addon = createInternalAddon({
...fakeAddon,
type: ADDON_TYPE_THEME,
theme_data: {
previewURL: 'https://addons.cdn.mozilla.net/user-media/addons/334902/preview_large.jpg?1313374873',
},
};
});
const root = render({ addon });

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

it('displays a message if the theme preview image is unavailable', () => {
const addon = { ...fakeAddon, type: ADDON_TYPE_THEME };
const addon = createInternalAddon({
...fakeAddon,
type: ADDON_TYPE_THEME,
});
const root = render({ addon });

expect(root.find('.SearchResult-notheme'))
Expand Down
20 changes: 12 additions & 8 deletions tests/unit/amo/reducers/testLanding.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { getLanding } from 'amo/actions/landing';
import landing, { initialState } from 'amo/reducers/landing';
import { ADDON_TYPE_THEME } from 'core/constants';
import { createInternalAddon } from 'core/reducers/addons';
import { fakeAddon } from 'tests/unit/amo/helpers';


describe('landing reducer', () => {
describe(__filename, () => {
it('defaults to not loading', () => {
const { loading } = landing(undefined, { type: 'unrelated' });

Expand Down Expand Up @@ -84,9 +85,9 @@ describe('landing reducer', () => {
it('sets the results', () => {
const entities = {
addons: {
bar: { slug: 'bar' },
foo: { slug: 'foo' },
food: { slug: 'food' },
bar: { ...fakeAddon, slug: 'bar' },
foo: { ...fakeAddon, slug: 'foo' },
food: { ...fakeAddon, slug: 'food' },
},
};
const state = landing(initialState, {
Expand All @@ -103,7 +104,10 @@ describe('landing reducer', () => {
});
expect(state.featured.count).toEqual(2);
expect(state.featured.results)
.toEqual([{ slug: 'foo' }, { slug: 'food' }]);
.toEqual([
createInternalAddon({ ...fakeAddon, slug: 'foo' }),
createInternalAddon({ ...fakeAddon, slug: 'food' }),
]);
expect(state.highlyRated).toEqual({ count: 0, results: [] });
expect(state.popular).toEqual({ count: 0, results: [] });
expect(state.resultsLoaded).toEqual(true);
Expand All @@ -112,9 +116,9 @@ describe('landing reducer', () => {
it('does not set null keys', () => {
const entities = {
addons: {
bar: { slug: 'bar' },
foo: { slug: 'foo' },
food: { slug: 'food' },
bar: { ...fakeAddon, slug: 'bar' },
foo: { ...fakeAddon, slug: 'foo' },
food: { ...fakeAddon, slug: 'food' },
},
};
const { highlyRated } = landing({
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/amo/reducers/test_addonsByAuthors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import reducer, {
initialState,
loadOtherAddonsByAuthors,
} from 'amo/reducers/addonsByAuthors';
import { createInternalAddon } from 'core/reducers/addons';
import { fakeAddon } from 'tests/unit/amo/helpers';


Expand Down Expand Up @@ -42,7 +43,7 @@ describe(__filename, () => {
addons: [fakeAddon],
}));
expect(state.byAddonSlug).toEqual({
'addon-slug': [fakeAddon],
'addon-slug': [createInternalAddon(fakeAddon)],
});
});

Expand Down Expand Up @@ -75,7 +76,7 @@ describe(__filename, () => {
slug,
}));
expect(previousState.byAddonSlug)
.toEqual({ 'addon-slug': [fakeAddon] });
.toEqual({ 'addon-slug': [createInternalAddon(fakeAddon)] });

const state = reducer(previousState, fetchOtherAddonsByAuthors({
authors: ['author1'],
Expand Down

0 comments on commit 3d5f3d1

Please sign in to comment.