Merge pull request #4571 from mozilla/chore/4556-fetchaddonsbyauthors
chore: Allow fetchAddonsByAuthors to search for add-ons without specifying an `exclude_addon` param
This commit is contained in:
Коммит
3840d55449
|
@ -158,6 +158,7 @@
|
|||
"classnames": "2.2.5",
|
||||
"common-tags": "1.7.2",
|
||||
"config": "1.29.2",
|
||||
"deepcopy": "0.6.3",
|
||||
"deep-eql": "3.0.1",
|
||||
"dompurify": "1.0.2",
|
||||
"es6-error": "4.1.0",
|
||||
|
@ -239,7 +240,6 @@
|
|||
"content-security-policy-parser": "^0.1.0",
|
||||
"cookie": "^0.3.1",
|
||||
"css-loader": "^0.28.3",
|
||||
"deepcopy": "^0.6.3",
|
||||
"enzyme": "^3.2.0",
|
||||
"enzyme-adapter-react-16": "^1.1.0",
|
||||
"eslint": "^4.15.0",
|
||||
|
|
|
@ -20,7 +20,10 @@ import PermissionsCard from 'amo/components/PermissionsCard';
|
|||
import DefaultRatingManager from 'amo/components/RatingManager';
|
||||
import ScreenShots from 'amo/components/ScreenShots';
|
||||
import Link from 'amo/components/Link';
|
||||
import { fetchOtherAddonsByAuthors } from 'amo/reducers/addonsByAuthors';
|
||||
import {
|
||||
fetchAddonsByAuthors,
|
||||
getAddonsForSlug,
|
||||
} from 'amo/reducers/addonsByAuthors';
|
||||
import {
|
||||
fetchAddon,
|
||||
getAddonByID,
|
||||
|
@ -119,7 +122,7 @@ export class AddonBase extends React.Component {
|
|||
}
|
||||
|
||||
dispatch(setViewContext(addon.type));
|
||||
this.dispatchFetchOtherAddonsByAuthors({ addon });
|
||||
this.dispatchFetchAddonsByAuthors({ addon });
|
||||
} else {
|
||||
dispatch(fetchAddon({ slug: params.slug, errorHandler }));
|
||||
}
|
||||
|
@ -139,7 +142,7 @@ export class AddonBase extends React.Component {
|
|||
}
|
||||
|
||||
if (newAddon && oldAddon !== newAddon) {
|
||||
this.dispatchFetchOtherAddonsByAuthors({ addon: newAddon });
|
||||
this.dispatchFetchAddonsByAuthors({ addon: newAddon });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,14 +162,14 @@ export class AddonBase extends React.Component {
|
|||
this.props.toggleThemePreview(event.currentTarget);
|
||||
}
|
||||
|
||||
dispatchFetchOtherAddonsByAuthors({ addon }) {
|
||||
dispatchFetchAddonsByAuthors({ addon }) {
|
||||
const { dispatch, errorHandler } = this.props;
|
||||
|
||||
dispatch(fetchOtherAddonsByAuthors({
|
||||
dispatch(fetchAddonsByAuthors({
|
||||
addonType: addon.type,
|
||||
authors: addon.authors.map((author) => author.username),
|
||||
errorHandlerId: errorHandler.id,
|
||||
slug: addon.slug,
|
||||
forAddonSlug: addon.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -629,7 +632,7 @@ export function mapStateToProps(state, ownProps) {
|
|||
let installedAddon = {};
|
||||
|
||||
if (addon) {
|
||||
addonsByAuthors = state.addonsByAuthors.byAddonSlug[addon.slug];
|
||||
addonsByAuthors = getAddonsForSlug(state.addonsByAuthors, addon.slug);
|
||||
installedAddon = state.installations[addon.guid] || {};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,125 +1,166 @@
|
|||
/* @flow */
|
||||
import type { AddonType, ExternalAddonType } from 'core/types/addons';
|
||||
import deepcopy from 'deepcopy';
|
||||
import invariant from 'invariant';
|
||||
|
||||
import { createInternalAddon } from 'core/reducers/addons';
|
||||
import type { AddonType, ExternalAddonType } from 'core/types/addons';
|
||||
|
||||
|
||||
type State = {
|
||||
byAddonSlug: { [string]: AddonType },
|
||||
// TODO: It might be nice to eventually stop storing add-ons in this
|
||||
// reducer at all and rely on the add-ons in the `addons` reducer.
|
||||
// That said, these are partial add-ons returned from the search
|
||||
// results and fetching all add-on data for each add-on might be too
|
||||
// expensive.
|
||||
byAddonId: { [number]: Array<AddonType> },
|
||||
byAddonSlug: { [string]: Array<number> },
|
||||
byUserId: { [number]: Array<number> },
|
||||
byUsername: { [string]: Array<number> },
|
||||
};
|
||||
|
||||
export const initialState: State = {
|
||||
byAddonId: {},
|
||||
byAddonSlug: {},
|
||||
byUserId: {},
|
||||
byUsername: {},
|
||||
};
|
||||
|
||||
export const OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE = 6;
|
||||
export const ADDONS_BY_AUTHORS_PAGE_SIZE = 6;
|
||||
|
||||
// For further information about this notation, see:
|
||||
// https://github.com/mozilla/addons-frontend/pull/3027#discussion_r137661289
|
||||
export const FETCH_OTHER_ADDONS_BY_AUTHORS: 'FETCH_OTHER_ADDONS_BY_AUTHORS'
|
||||
= 'FETCH_OTHER_ADDONS_BY_AUTHORS';
|
||||
export const LOAD_OTHER_ADDONS_BY_AUTHORS: 'LOAD_OTHER_ADDONS_BY_AUTHORS'
|
||||
= 'LOAD_OTHER_ADDONS_BY_AUTHORS';
|
||||
export const FETCH_ADDONS_BY_AUTHORS: 'FETCH_ADDONS_BY_AUTHORS'
|
||||
= 'FETCH_ADDONS_BY_AUTHORS';
|
||||
export const LOAD_ADDONS_BY_AUTHORS: 'LOAD_ADDONS_BY_AUTHORS'
|
||||
= 'LOAD_ADDONS_BY_AUTHORS';
|
||||
|
||||
type FetchOtherAddonsByAuthorsParams = {|
|
||||
type FetchAddonsByAuthorsParams = {|
|
||||
addonType: string,
|
||||
authors: Array<string>,
|
||||
errorHandlerId: string,
|
||||
slug: string,
|
||||
forAddonSlug?: string,
|
||||
|};
|
||||
|
||||
type FetchOtherAddonsByAuthorsAction = {|
|
||||
type: typeof FETCH_OTHER_ADDONS_BY_AUTHORS,
|
||||
payload: FetchOtherAddonsByAuthorsParams,
|
||||
type FetchAddonsByAuthorsAction = {|
|
||||
type: typeof FETCH_ADDONS_BY_AUTHORS,
|
||||
payload: FetchAddonsByAuthorsParams,
|
||||
|};
|
||||
|
||||
export const fetchOtherAddonsByAuthors = (
|
||||
{ addonType, authors, errorHandlerId, slug }: FetchOtherAddonsByAuthorsParams
|
||||
): FetchOtherAddonsByAuthorsAction => {
|
||||
if (!errorHandlerId) {
|
||||
throw new Error('An errorHandlerId is required');
|
||||
}
|
||||
|
||||
if (!slug) {
|
||||
throw new Error('An add-on slug is required.');
|
||||
}
|
||||
|
||||
if (!addonType) {
|
||||
throw new Error('An add-on type is required.');
|
||||
}
|
||||
|
||||
if (!authors) {
|
||||
throw new Error('Authors are required.');
|
||||
}
|
||||
|
||||
if (!Array.isArray(authors)) {
|
||||
throw new Error('The authors parameter must be an array.');
|
||||
}
|
||||
export const fetchAddonsByAuthors = (
|
||||
{ addonType, authors, errorHandlerId, forAddonSlug }: FetchAddonsByAuthorsParams
|
||||
): FetchAddonsByAuthorsAction => {
|
||||
invariant(errorHandlerId, 'An errorHandlerId is required');
|
||||
invariant(addonType, 'An add-on type is required.');
|
||||
invariant(authors, 'Authors are required.');
|
||||
invariant(Array.isArray(authors), 'The authors parameter must be an array.');
|
||||
|
||||
return {
|
||||
type: FETCH_OTHER_ADDONS_BY_AUTHORS,
|
||||
type: FETCH_ADDONS_BY_AUTHORS,
|
||||
payload: {
|
||||
addonType,
|
||||
authors,
|
||||
errorHandlerId,
|
||||
slug,
|
||||
forAddonSlug,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type LoadOtherAddonsByAuthorsParams = {|
|
||||
slug: string,
|
||||
type LoadAddonsByAuthorsParams = {|
|
||||
addons: Array<ExternalAddonType>,
|
||||
forAddonSlug?: string,
|
||||
|};
|
||||
|
||||
type LoadOtherAddonsByAuthorsAction = {|
|
||||
type: typeof LOAD_OTHER_ADDONS_BY_AUTHORS,
|
||||
payload: LoadOtherAddonsByAuthorsParams,
|
||||
type LoadAddonsByAuthorsAction = {|
|
||||
type: typeof LOAD_ADDONS_BY_AUTHORS,
|
||||
payload: LoadAddonsByAuthorsParams,
|
||||
|};
|
||||
|
||||
export const loadOtherAddonsByAuthors = (
|
||||
{ addons, slug }: LoadOtherAddonsByAuthorsParams
|
||||
): LoadOtherAddonsByAuthorsAction => {
|
||||
if (!slug) {
|
||||
throw new Error('An add-on slug is required.');
|
||||
}
|
||||
|
||||
if (!addons) {
|
||||
throw new Error('A set of add-ons is required.');
|
||||
}
|
||||
export const loadAddonsByAuthors = (
|
||||
{ addons, forAddonSlug }: LoadAddonsByAuthorsParams
|
||||
): LoadAddonsByAuthorsAction => {
|
||||
invariant(addons, 'A set of add-ons is required.');
|
||||
|
||||
return {
|
||||
type: LOAD_OTHER_ADDONS_BY_AUTHORS,
|
||||
payload: { slug, addons },
|
||||
type: LOAD_ADDONS_BY_AUTHORS,
|
||||
payload: { addons, forAddonSlug },
|
||||
};
|
||||
};
|
||||
|
||||
export const getAddonsForSlug = (state: State, slug: string) => {
|
||||
const ids = state.byAddonSlug[slug];
|
||||
|
||||
return ids ? ids.map((id) => {
|
||||
return state.byAddonId[id];
|
||||
}) : null;
|
||||
};
|
||||
|
||||
type Action =
|
||||
| FetchOtherAddonsByAuthorsAction
|
||||
| LoadOtherAddonsByAuthorsAction;
|
||||
| FetchAddonsByAuthorsAction
|
||||
| LoadAddonsByAuthorsAction;
|
||||
|
||||
const reducer = (
|
||||
state: State = initialState,
|
||||
action: Action
|
||||
): State => {
|
||||
switch (action.type) {
|
||||
case FETCH_OTHER_ADDONS_BY_AUTHORS:
|
||||
return {
|
||||
...state,
|
||||
byAddonSlug: {
|
||||
...state.byAddonSlug,
|
||||
[action.payload.slug]: undefined,
|
||||
},
|
||||
};
|
||||
case LOAD_OTHER_ADDONS_BY_AUTHORS:
|
||||
return {
|
||||
...state,
|
||||
byAddonSlug: {
|
||||
...state.byAddonSlug,
|
||||
[action.payload.slug]: action.payload.addons
|
||||
.slice(0, OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE)
|
||||
.map((addon) => createInternalAddon(addon)),
|
||||
},
|
||||
};
|
||||
case FETCH_ADDONS_BY_AUTHORS: {
|
||||
const newState = deepcopy(state);
|
||||
|
||||
if (action.payload.forAddonSlug) {
|
||||
newState.byAddonSlug = {
|
||||
...newState.byAddonSlug,
|
||||
[action.payload.forAddonSlug]: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// Reset the data for each author requested.
|
||||
for (const authorUsername of action.payload.authors) {
|
||||
// TODO: Reset the userId here too.
|
||||
// See: https://github.com/mozilla/addons-frontend/issues/4602
|
||||
newState.byUsername[authorUsername] = undefined;
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
case LOAD_ADDONS_BY_AUTHORS: {
|
||||
const newState = deepcopy(state);
|
||||
|
||||
if (action.payload.forAddonSlug) {
|
||||
newState.byAddonSlug = {
|
||||
[action.payload.forAddonSlug]: action.payload.addons
|
||||
.slice(0, ADDONS_BY_AUTHORS_PAGE_SIZE)
|
||||
.map((addon) => addon.id),
|
||||
};
|
||||
}
|
||||
|
||||
const addons = action.payload.addons
|
||||
.map((addon) => createInternalAddon(addon));
|
||||
|
||||
for (const addon of addons) {
|
||||
newState.byAddonId[addon.id] = addon;
|
||||
|
||||
if (addon.authors) {
|
||||
for (const author of addon.authors) {
|
||||
if (!newState.byUserId[author.id]) {
|
||||
newState.byUserId[author.id] = [];
|
||||
}
|
||||
if (!newState.byUsername[author.username]) {
|
||||
newState.byUsername[author.username] = [];
|
||||
}
|
||||
|
||||
if (!newState.byUserId[author.id].includes(addon.id)) {
|
||||
newState.byUserId[author.id].push(addon.id);
|
||||
}
|
||||
|
||||
if (!newState.byUsername[author.username].includes(addon.id)) {
|
||||
newState.byUsername[author.username].push(addon.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { call, put, select, takeLatest } from 'redux-saga/effects';
|
||||
import { SEARCH_SORT_TRENDING } from 'core/constants';
|
||||
import {
|
||||
FETCH_OTHER_ADDONS_BY_AUTHORS,
|
||||
OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
loadOtherAddonsByAuthors,
|
||||
FETCH_ADDONS_BY_AUTHORS,
|
||||
ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
loadAddonsByAuthors,
|
||||
} from 'amo/reducers/addonsByAuthors';
|
||||
import { search as searchApi } from 'core/api/search';
|
||||
import log from 'core/logger';
|
||||
import { createErrorHandler, getState } from 'core/sagas/utils';
|
||||
|
||||
|
||||
export function* fetchOtherAddonsByAuthors({ payload }) {
|
||||
const { errorHandlerId, authors, slug, addonType } = payload;
|
||||
export function* fetchAddonsByAuthors({ payload }) {
|
||||
const { errorHandlerId, authors, addonType, forAddonSlug } = payload;
|
||||
const errorHandler = createErrorHandler(errorHandlerId);
|
||||
|
||||
yield put(errorHandler.createClearingAction());
|
||||
|
@ -24,8 +24,8 @@ export function* fetchOtherAddonsByAuthors({ payload }) {
|
|||
filters: {
|
||||
addonType,
|
||||
author: authors.join(','),
|
||||
exclude_addons: slug,
|
||||
page_size: OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
exclude_addons: forAddonSlug,
|
||||
page_size: ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
sort: SEARCH_SORT_TRENDING,
|
||||
},
|
||||
});
|
||||
|
@ -34,7 +34,7 @@ export function* fetchOtherAddonsByAuthors({ payload }) {
|
|||
// https://github.com/mozilla/addons-frontend/issues/2917 is done.
|
||||
const addons = Object.values(response.entities.addons || {});
|
||||
|
||||
yield put(loadOtherAddonsByAuthors({ addons, slug }));
|
||||
yield put(loadAddonsByAuthors({ addons, forAddonSlug }));
|
||||
} catch (error) {
|
||||
log.warn(`Search for addons by authors results failed to load: ${error}`);
|
||||
yield put(errorHandler.createErrorAction(error));
|
||||
|
@ -42,5 +42,5 @@ export function* fetchOtherAddonsByAuthors({ payload }) {
|
|||
}
|
||||
|
||||
export default function* addonsByAuthorsSaga() {
|
||||
yield takeLatest(FETCH_OTHER_ADDONS_BY_AUTHORS, fetchOtherAddonsByAuthors);
|
||||
yield takeLatest(FETCH_ADDONS_BY_AUTHORS, fetchAddonsByAuthors);
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ import {
|
|||
createInternalAddon, fetchAddon as fetchAddonAction, loadAddons,
|
||||
} from 'core/reducers/addons';
|
||||
import {
|
||||
fetchOtherAddonsByAuthors,
|
||||
loadOtherAddonsByAuthors,
|
||||
fetchAddonsByAuthors,
|
||||
loadAddonsByAuthors,
|
||||
} from 'amo/reducers/addonsByAuthors';
|
||||
import { setError } from 'core/actions/errors';
|
||||
import { setInstallState } from 'core/actions/installations';
|
||||
|
@ -140,10 +140,10 @@ describe(__filename, () => {
|
|||
return loadAddons(createFetchAddonResult(addon).entities);
|
||||
};
|
||||
|
||||
const _loadOtherAddonsByAuthors = ({ addon, addonsByAuthors }) => {
|
||||
return loadOtherAddonsByAuthors({
|
||||
slug: addon.slug,
|
||||
const _loadAddonsByAuthors = ({ addon, addonsByAuthors }) => {
|
||||
return loadAddonsByAuthors({
|
||||
addons: addonsByAuthors,
|
||||
forAddonSlug: addon.slug,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1064,7 +1064,7 @@ describe(__filename, () => {
|
|||
store.dispatch(_loadAddons({ addon }));
|
||||
|
||||
if (addonsByAuthors) {
|
||||
store.dispatch(_loadOtherAddonsByAuthors({ addon, addonsByAuthors }));
|
||||
store.dispatch(_loadAddonsByAuthors({ addon, addonsByAuthors }));
|
||||
}
|
||||
|
||||
return { store };
|
||||
|
@ -1084,11 +1084,11 @@ describe(__filename, () => {
|
|||
|
||||
renderComponent({ params: { slug: addon.slug }, store });
|
||||
|
||||
sinon.assert.calledWith(fakeDispatch, fetchOtherAddonsByAuthors({
|
||||
sinon.assert.calledWith(fakeDispatch, fetchAddonsByAuthors({
|
||||
addonType: addon.type,
|
||||
authors: addon.authors.map((author) => author.username),
|
||||
errorHandlerId: createStubErrorHandler().id,
|
||||
slug: addon.slug,
|
||||
forAddonSlug: addon.slug,
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -1128,11 +1128,11 @@ describe(__filename, () => {
|
|||
).addon;
|
||||
root.setProps({ addon: addonFromState });
|
||||
|
||||
sinon.assert.calledWith(fakeDispatch, fetchOtherAddonsByAuthors({
|
||||
sinon.assert.calledWith(fakeDispatch, fetchAddonsByAuthors({
|
||||
addonType: newAddon.type,
|
||||
authors: newAddon.authors.map((author) => author.username),
|
||||
errorHandlerId: createStubErrorHandler().id,
|
||||
slug: newAddon.slug,
|
||||
forAddonSlug: newAddon.slug,
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -1140,7 +1140,7 @@ describe(__filename, () => {
|
|||
const addon = fakeTheme;
|
||||
const { store } = dispatchAddonData({
|
||||
addon,
|
||||
addonsByAuthors: [{ ...fakeTheme, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
|
||||
const root = renderComponent({ params: { slug: addon.slug }, store });
|
||||
|
@ -1153,7 +1153,7 @@ describe(__filename, () => {
|
|||
const addon = fakeAddon;
|
||||
const { store } = dispatchAddonData({
|
||||
addon,
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
|
||||
const root = renderComponent({ params: { slug: addon.slug }, store });
|
||||
|
@ -1185,7 +1185,7 @@ describe(__filename, () => {
|
|||
it('displays the developer name when add-on is an extension', () => {
|
||||
const root = renderMoreAddons({
|
||||
addon: fakeAddon,
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More extensions by Krupa');
|
||||
});
|
||||
|
@ -1193,7 +1193,7 @@ describe(__filename, () => {
|
|||
it('displays the translator name when add-on is a dictionary', () => {
|
||||
const root = renderMoreAddons({
|
||||
addon: { ...fakeAddon, type: ADDON_TYPE_DICT },
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More dictionaries by Krupa');
|
||||
});
|
||||
|
@ -1201,7 +1201,7 @@ describe(__filename, () => {
|
|||
it('displays the translator name when add-on is a language pack', () => {
|
||||
const root = renderMoreAddons({
|
||||
addon: { ...fakeAddon, type: ADDON_TYPE_LANG },
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More language packs by Krupa');
|
||||
});
|
||||
|
@ -1209,7 +1209,7 @@ describe(__filename, () => {
|
|||
it('displays the artist name when add-on is a theme', () => {
|
||||
const root = renderMoreAddons({
|
||||
addon: fakeTheme,
|
||||
addonsByAuthors: [{ ...fakeTheme, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeTheme, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More themes by MaDonna');
|
||||
});
|
||||
|
@ -1217,7 +1217,7 @@ describe(__filename, () => {
|
|||
it('displays the author name in any other cases', () => {
|
||||
const root = renderMoreAddons({
|
||||
addon: { ...fakeAddon, type: ADDON_TYPE_OPENSEARCH },
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More add-ons by Krupa');
|
||||
});
|
||||
|
@ -1228,7 +1228,7 @@ describe(__filename, () => {
|
|||
...fakeAddon,
|
||||
authors: Array(2).fill(fakeAddon.authors[0]),
|
||||
},
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More extensions by these developers');
|
||||
});
|
||||
|
@ -1239,7 +1239,7 @@ describe(__filename, () => {
|
|||
...fakeTheme,
|
||||
authors: Array(2).fill(fakeTheme.authors[0]),
|
||||
},
|
||||
addonsByAuthors: [{ ...fakeTheme, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeTheme, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More themes by these artists');
|
||||
});
|
||||
|
@ -1251,7 +1251,7 @@ describe(__filename, () => {
|
|||
authors: Array(2).fill(fakeAddon.authors[0]),
|
||||
type: ADDON_TYPE_LANG,
|
||||
},
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root)
|
||||
.toHaveProp('header', 'More language packs by these translators');
|
||||
|
@ -1264,7 +1264,7 @@ describe(__filename, () => {
|
|||
authors: Array(2).fill(fakeAddon.authors[0]),
|
||||
type: ADDON_TYPE_DICT,
|
||||
},
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root)
|
||||
.toHaveProp('header', 'More dictionaries by these translators');
|
||||
|
@ -1277,7 +1277,7 @@ describe(__filename, () => {
|
|||
authors: Array(2).fill(fakeAddon.authors[0]),
|
||||
type: ADDON_TYPE_OPENSEARCH,
|
||||
},
|
||||
addonsByAuthors: [{ ...fakeAddon, slug: 'another-slug' }],
|
||||
addonsByAuthors: [{ ...fakeAddon, forAddonSlug: 'another-slug' }],
|
||||
});
|
||||
expect(root).toHaveProp('header', 'More add-ons by these developers');
|
||||
});
|
||||
|
@ -1285,9 +1285,9 @@ describe(__filename, () => {
|
|||
it('displays more add-ons by authors', () => {
|
||||
const addon = fakeAddon;
|
||||
const addonsByAuthors = [
|
||||
{ ...fakeAddon, slug: 'addon-1' },
|
||||
{ ...fakeAddon, slug: 'addon-2' },
|
||||
{ ...fakeAddon, slug: 'addon-3' },
|
||||
{ ...fakeAddon, slug: 'addon-1', id: 1 },
|
||||
{ ...fakeAddon, slug: 'addon-2', id: 2 },
|
||||
{ ...fakeAddon, slug: 'addon-3', id: 3 },
|
||||
];
|
||||
|
||||
const root = renderMoreAddons({ addon, addonsByAuthors });
|
||||
|
@ -1301,9 +1301,9 @@ describe(__filename, () => {
|
|||
it('indicates when other add-ons are themes', () => {
|
||||
const addon = fakeTheme;
|
||||
const addonsByAuthors = [
|
||||
{ ...fakeTheme, slug: 'addon-1' },
|
||||
{ ...fakeTheme, slug: 'addon-2' },
|
||||
{ ...fakeTheme, slug: 'addon-3' },
|
||||
{ ...fakeTheme },
|
||||
{ ...fakeTheme },
|
||||
{ ...fakeTheme },
|
||||
];
|
||||
|
||||
const root = renderMoreAddons({ addon, addonsByAuthors });
|
||||
|
|
|
@ -46,14 +46,16 @@ export const fakePlatformFile = Object.freeze({
|
|||
url: 'https://a.m.o/files/321/addon.xpi',
|
||||
});
|
||||
|
||||
export const fakeAuthor = Object.freeze({
|
||||
id: 98811255,
|
||||
name: 'Krupa',
|
||||
picture_url: 'https://addons.cdn.mozilla.net/static/img/anon_user.png',
|
||||
url: 'http://olympia.test/en-US/firefox/user/krupa/',
|
||||
username: 'krupa',
|
||||
});
|
||||
|
||||
export const fakeAddon = Object.freeze({
|
||||
authors: [{
|
||||
id: 98811255,
|
||||
name: 'Krupa',
|
||||
picture_url: 'https://addons.cdn.mozilla.net/static/img/anon_user.png',
|
||||
url: 'http://olympia.test/en-US/firefox/user/krupa/',
|
||||
username: 'krupa',
|
||||
}],
|
||||
authors: [fakeAuthor],
|
||||
average_daily_users: 100,
|
||||
categories: { firefox: ['other'] },
|
||||
current_beta_version: null,
|
||||
|
|
|
@ -1,15 +1,35 @@
|
|||
import { ADDON_TYPE_THEME } from 'core/constants';
|
||||
import reducer, {
|
||||
OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
fetchOtherAddonsByAuthors,
|
||||
ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
fetchAddonsByAuthors,
|
||||
getAddonsForSlug,
|
||||
initialState,
|
||||
loadOtherAddonsByAuthors,
|
||||
loadAddonsByAuthors,
|
||||
} from 'amo/reducers/addonsByAuthors';
|
||||
import { createInternalAddon } from 'core/reducers/addons';
|
||||
import { fakeAddon } from 'tests/unit/amo/helpers';
|
||||
import { fakeAddon, fakeAuthor } from 'tests/unit/amo/helpers';
|
||||
|
||||
|
||||
describe(__filename, () => {
|
||||
function fakeAddons() {
|
||||
const firstAddon = {
|
||||
...fakeAddon,
|
||||
id: 6,
|
||||
authors: [
|
||||
{ username: 'test', id: 51 },
|
||||
{ username: 'test2', id: 61 },
|
||||
],
|
||||
};
|
||||
const secondAddon = {
|
||||
...fakeAddon, id: 7, authors: [{ username: 'test2', id: 61 }],
|
||||
};
|
||||
const thirdAddon = {
|
||||
...fakeAddon, id: 8, authors: [{ username: 'test3', id: 71 }],
|
||||
};
|
||||
|
||||
return { firstAddon, secondAddon, thirdAddon };
|
||||
}
|
||||
|
||||
describe('reducer', () => {
|
||||
it('initializes properly', () => {
|
||||
const state = reducer(undefined, {});
|
||||
|
@ -19,8 +39,8 @@ describe(__filename, () => {
|
|||
it('ignores unrelated actions', () => {
|
||||
// Load some initial state to be sure that an unrelated action does not
|
||||
// change it.
|
||||
const state = reducer(undefined, loadOtherAddonsByAuthors({
|
||||
slug: fakeAddon.slug,
|
||||
const state = reducer(undefined, loadAddonsByAuthors({
|
||||
forAddonSlug: fakeAddon.slug,
|
||||
addons: [fakeAddon],
|
||||
}));
|
||||
const newState = reducer(state, { type: 'UNRELATED' });
|
||||
|
@ -28,8 +48,8 @@ describe(__filename, () => {
|
|||
});
|
||||
|
||||
it('allows an empty list of add-ons', () => {
|
||||
const state = reducer(undefined, loadOtherAddonsByAuthors({
|
||||
slug: 'addon-slug',
|
||||
const state = reducer(undefined, loadAddonsByAuthors({
|
||||
forAddonSlug: 'addon-slug',
|
||||
addons: [],
|
||||
}));
|
||||
expect(state.byAddonSlug).toEqual({
|
||||
|
@ -38,120 +58,174 @@ describe(__filename, () => {
|
|||
});
|
||||
|
||||
it('adds related add-ons by slug', () => {
|
||||
const state = reducer(undefined, loadOtherAddonsByAuthors({
|
||||
slug: 'addon-slug',
|
||||
const state = reducer(undefined, loadAddonsByAuthors({
|
||||
forAddonSlug: 'addon-slug',
|
||||
addons: [fakeAddon],
|
||||
}));
|
||||
expect(state.byAddonSlug).toEqual({
|
||||
'addon-slug': [createInternalAddon(fakeAddon)],
|
||||
'addon-slug': [fakeAddon.id],
|
||||
});
|
||||
});
|
||||
|
||||
it('always ensures the page size is consistent', () => {
|
||||
const slug = 'addon-slug';
|
||||
const state = reducer(undefined, loadOtherAddonsByAuthors({
|
||||
slug,
|
||||
const forAddonSlug = 'addon-slug';
|
||||
const state = reducer(undefined, loadAddonsByAuthors({
|
||||
forAddonSlug,
|
||||
// This is the case where there are more add-ons loaded than needed.
|
||||
addons: Array(OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE + 2).fill(fakeAddon),
|
||||
addons: Array(ADDONS_BY_AUTHORS_PAGE_SIZE + 2).fill(fakeAddon),
|
||||
}));
|
||||
expect(state.byAddonSlug[slug])
|
||||
.toHaveLength(OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE);
|
||||
expect(state.byAddonSlug[forAddonSlug])
|
||||
.toHaveLength(ADDONS_BY_AUTHORS_PAGE_SIZE);
|
||||
});
|
||||
|
||||
it('returns state if no excluded slug is specified', () => {
|
||||
const forAddonSlug = 'addon-slug';
|
||||
|
||||
const previousState = reducer(undefined, loadAddonsByAuthors({
|
||||
addons: [fakeAddon],
|
||||
forAddonSlug,
|
||||
}));
|
||||
expect(previousState.byAddonSlug)
|
||||
.toEqual({ 'addon-slug': [fakeAddon.id] });
|
||||
|
||||
const state = reducer(previousState, fetchAddonsByAuthors({
|
||||
authors: ['author2'],
|
||||
addonType: ADDON_TYPE_THEME,
|
||||
errorHandlerId: 'error-handler-id',
|
||||
}));
|
||||
|
||||
expect(state.byAddonSlug).toEqual({ 'addon-slug': [fakeAddon.id] });
|
||||
});
|
||||
|
||||
it('resets the loaded add-ons', () => {
|
||||
const slug = 'addon-slug';
|
||||
const forAddonSlug = 'addon-slug';
|
||||
|
||||
const previousState = reducer(undefined, loadOtherAddonsByAuthors({
|
||||
const previousState = reducer(undefined, loadAddonsByAuthors({
|
||||
addons: [fakeAddon],
|
||||
slug,
|
||||
forAddonSlug,
|
||||
}));
|
||||
expect(previousState.byAddonSlug)
|
||||
.toEqual({ 'addon-slug': [createInternalAddon(fakeAddon)] });
|
||||
.toEqual({ 'addon-slug': [fakeAddon.id] });
|
||||
|
||||
const state = reducer(previousState, fetchOtherAddonsByAuthors({
|
||||
const state = reducer(previousState, fetchAddonsByAuthors({
|
||||
authors: ['author1'],
|
||||
addonType: ADDON_TYPE_THEME,
|
||||
errorHandlerId: 'error-handler-id',
|
||||
slug,
|
||||
forAddonSlug,
|
||||
}));
|
||||
expect(state.byAddonSlug)
|
||||
.toEqual({ 'addon-slug': undefined });
|
||||
|
||||
expect(state.byAddonSlug).toMatchObject({ 'addon-slug': undefined });
|
||||
expect(state.byUsername).toMatchObject({ author1: undefined });
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchOtherAddonsByAuthors()', () => {
|
||||
const getParams = () => {
|
||||
describe('loadAddonsByAuthors()', () => {
|
||||
const getParams = (extra = {}) => {
|
||||
return {
|
||||
authors: ['user1', 'user2'],
|
||||
addonType: ADDON_TYPE_THEME,
|
||||
errorHandlerId: 'error-handler-id',
|
||||
slug: 'addon-slug',
|
||||
addons: [],
|
||||
forAddonSlug: fakeAddon.slug,
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
|
||||
it('requires an error id', () => {
|
||||
const params = getParams();
|
||||
delete params.errorHandlerId;
|
||||
expect(() => {
|
||||
fetchOtherAddonsByAuthors(params);
|
||||
}).toThrow(/An errorHandlerId is required/);
|
||||
it('adds each add-on to each author array', () => {
|
||||
const firstAuthor = { ...fakeAuthor, id: 50 };
|
||||
const secondAuthor = { ...fakeAuthor, id: 60 };
|
||||
const multiAuthorAddon = {
|
||||
...fakeAddon,
|
||||
authors: [firstAuthor, secondAuthor],
|
||||
};
|
||||
const params = getParams({ addons: [multiAuthorAddon] });
|
||||
|
||||
const newState = reducer(undefined, loadAddonsByAuthors(params));
|
||||
|
||||
expect(newState.byUserId).toEqual({
|
||||
[firstAuthor.id]: [multiAuthorAddon.id],
|
||||
[secondAuthor.id]: [multiAuthorAddon.id],
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a slug', () => {
|
||||
const params = getParams();
|
||||
delete params.slug;
|
||||
expect(() => {
|
||||
fetchOtherAddonsByAuthors(params);
|
||||
}).toThrow(/An add-on slug is required/);
|
||||
it('adds each different add-on to the byAddonId dictionary', () => {
|
||||
const addons = fakeAddons();
|
||||
const params = getParams({
|
||||
addons: Object.values(addons),
|
||||
forAddonSlug: undefined,
|
||||
});
|
||||
|
||||
const newState = reducer(undefined, loadAddonsByAuthors(params));
|
||||
|
||||
expect(newState.byAddonId).toEqual({
|
||||
[addons.firstAddon.id]: createInternalAddon(addons.firstAddon),
|
||||
[addons.secondAddon.id]: createInternalAddon(addons.secondAddon),
|
||||
[addons.thirdAddon.id]: createInternalAddon(addons.thirdAddon),
|
||||
});
|
||||
});
|
||||
|
||||
it('requires an add-on type', () => {
|
||||
const params = getParams();
|
||||
delete params.addonType;
|
||||
expect(() => {
|
||||
fetchOtherAddonsByAuthors(params);
|
||||
}).toThrow(/An add-on type is required/);
|
||||
it('adds each different add-on to each author array', () => {
|
||||
// See fakeAddons() output, above.
|
||||
const firstAuthorId = 51;
|
||||
const secondAuthorId = 61;
|
||||
const thirdAuthorId = 71;
|
||||
const addons = fakeAddons();
|
||||
const params = getParams({
|
||||
addons: Object.values(addons),
|
||||
forAddonSlug: undefined,
|
||||
});
|
||||
|
||||
const newState = reducer(undefined, loadAddonsByAuthors(params));
|
||||
|
||||
expect(newState.byUserId).toEqual({
|
||||
[firstAuthorId]: [addons.firstAddon.id],
|
||||
[secondAuthorId]: [addons.firstAddon.id, addons.secondAddon.id],
|
||||
[thirdAuthorId]: [addons.thirdAddon.id],
|
||||
});
|
||||
});
|
||||
|
||||
it('requires some authors', () => {
|
||||
it('does not modify byAddonSlug if forAddonSlug is not set', () => {
|
||||
const params = getParams();
|
||||
delete params.authors;
|
||||
expect(() => {
|
||||
fetchOtherAddonsByAuthors(params);
|
||||
}).toThrow(/Authors are required/);
|
||||
delete params.forAddonSlug;
|
||||
|
||||
const newState = reducer(undefined, loadAddonsByAuthors(params));
|
||||
|
||||
expect(newState.byAddonSlug).toEqual(initialState.byAddonSlug);
|
||||
});
|
||||
|
||||
it('requires an array of authors', () => {
|
||||
const params = getParams();
|
||||
params.authors = 'invalid-type';
|
||||
expect(() => {
|
||||
fetchOtherAddonsByAuthors(params);
|
||||
}).toThrow(/The authors parameter must be an array/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadOtherAddonsByAuthors()', () => {
|
||||
const getParams = () => {
|
||||
return {
|
||||
it('modifies byAddonSlug if forAddonSlug is set', () => {
|
||||
const params = getParams({
|
||||
addons: [fakeAddon],
|
||||
slug: 'addon-slug',
|
||||
};
|
||||
};
|
||||
forAddonSlug: fakeAddon.slug,
|
||||
});
|
||||
|
||||
it('requires an add-on slug', () => {
|
||||
const params = getParams();
|
||||
delete params.slug;
|
||||
expect(() => {
|
||||
loadOtherAddonsByAuthors(params);
|
||||
}).toThrow(/An add-on slug is required/);
|
||||
const newState = reducer(undefined, loadAddonsByAuthors(params));
|
||||
|
||||
expect(newState.byAddonSlug)
|
||||
.toEqual({ [fakeAddon.slug]: [fakeAddon.id] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAddonsForSlug', () => {
|
||||
it('returns addons', () => {
|
||||
const addons = fakeAddons();
|
||||
const state = reducer(undefined, loadAddonsByAuthors({
|
||||
addons: Object.values(addons),
|
||||
forAddonSlug: 'test',
|
||||
}));
|
||||
|
||||
expect(getAddonsForSlug(state, 'test')).toEqual([
|
||||
createInternalAddon(addons.firstAddon),
|
||||
createInternalAddon(addons.secondAddon),
|
||||
createInternalAddon(addons.thirdAddon),
|
||||
]);
|
||||
});
|
||||
|
||||
it('requires an array of add-ons', () => {
|
||||
const params = getParams();
|
||||
delete params.addons;
|
||||
expect(() => {
|
||||
loadOtherAddonsByAuthors(params);
|
||||
}).toThrow(/A set of add-ons is required/);
|
||||
it('returns nothing if no add-ons are found', () => {
|
||||
const addons = fakeAddons();
|
||||
const state = reducer(undefined, loadAddonsByAuthors({
|
||||
addons: Object.values(addons),
|
||||
forAddonSlug: 'test',
|
||||
}));
|
||||
|
||||
expect(getAddonsForSlug(state, 'not-a-slug')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import SagaTester from 'redux-saga-tester';
|
||||
|
||||
import addonsByAuthorsReducer, {
|
||||
OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
fetchOtherAddonsByAuthors,
|
||||
loadOtherAddonsByAuthors,
|
||||
ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
fetchAddonsByAuthors,
|
||||
loadAddonsByAuthors,
|
||||
} from 'amo/reducers/addonsByAuthors';
|
||||
import addonsByAuthorsSaga from 'amo/sagas/addonsByAuthors';
|
||||
import {
|
||||
|
@ -38,8 +38,8 @@ describe(__filename, () => {
|
|||
sagaTester.start(addonsByAuthorsSaga);
|
||||
});
|
||||
|
||||
function _fetchOtherAddonsByAuthors(params) {
|
||||
sagaTester.dispatch(fetchOtherAddonsByAuthors({
|
||||
function _fetchAddonsByAuthors(params) {
|
||||
sagaTester.dispatch(fetchAddonsByAuthors({
|
||||
errorHandlerId: errorHandler.id,
|
||||
addonType: ADDON_TYPE_THEME,
|
||||
...params,
|
||||
|
@ -47,6 +47,39 @@ describe(__filename, () => {
|
|||
}
|
||||
|
||||
it('calls the API to retrieve other add-ons', async () => {
|
||||
const addons = [fakeAddon];
|
||||
const authors = ['mozilla', 'johnedoe'];
|
||||
const state = sagaTester.getState();
|
||||
|
||||
mockApi
|
||||
.expects('search')
|
||||
.withArgs({
|
||||
api: state.api,
|
||||
filters: {
|
||||
addonType: ADDON_TYPE_THEME,
|
||||
author: authors.join(','),
|
||||
exclude_addons: undefined, // `callApi` will internally unset this
|
||||
page_size: ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
sort: SEARCH_SORT_TRENDING,
|
||||
},
|
||||
})
|
||||
.once()
|
||||
.returns(Promise.resolve(createAddonsApiResult(addons)));
|
||||
|
||||
_fetchAddonsByAuthors({ authors });
|
||||
|
||||
const expectedLoadAction = loadAddonsByAuthors({
|
||||
addons,
|
||||
forAddonSlug: undefined,
|
||||
});
|
||||
|
||||
const loadAction = await sagaTester.waitFor(expectedLoadAction.type);
|
||||
mockApi.verify();
|
||||
|
||||
expect(loadAction).toEqual(expectedLoadAction);
|
||||
});
|
||||
|
||||
it('sends `exclude_addons` param if `forAddonSlug` is set', async () => {
|
||||
const addons = [fakeAddon];
|
||||
const authors = ['mozilla', 'johnedoe'];
|
||||
const { slug } = fakeAddon;
|
||||
|
@ -60,32 +93,34 @@ describe(__filename, () => {
|
|||
addonType: ADDON_TYPE_THEME,
|
||||
author: authors.join(','),
|
||||
exclude_addons: slug,
|
||||
page_size: OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
page_size: ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
sort: SEARCH_SORT_TRENDING,
|
||||
},
|
||||
})
|
||||
.once()
|
||||
.returns(Promise.resolve(createAddonsApiResult(addons)));
|
||||
|
||||
_fetchOtherAddonsByAuthors({ authors, slug });
|
||||
_fetchAddonsByAuthors({ authors, forAddonSlug: slug });
|
||||
|
||||
const expectedLoadAction = loadOtherAddonsByAuthors({ addons, slug });
|
||||
const expectedLoadAction = loadAddonsByAuthors({
|
||||
addons,
|
||||
forAddonSlug: slug,
|
||||
});
|
||||
|
||||
await sagaTester.waitFor(expectedLoadAction.type);
|
||||
const loadAction = await sagaTester.waitFor(expectedLoadAction.type);
|
||||
mockApi.verify();
|
||||
|
||||
const loadAction = sagaTester.getCalledActions()[2];
|
||||
expect(loadAction).toEqual(expectedLoadAction);
|
||||
});
|
||||
|
||||
it('clears the error handler', async () => {
|
||||
_fetchOtherAddonsByAuthors({ authors: [], slug: fakeAddon.slug });
|
||||
_fetchAddonsByAuthors({ authors: [], forAddonSlug: fakeAddon.slug });
|
||||
|
||||
const expectedAction = errorHandler.createClearingAction();
|
||||
|
||||
await sagaTester.waitFor(expectedAction.type);
|
||||
expect(sagaTester.getCalledActions()[1])
|
||||
.toEqual(errorHandler.createClearingAction());
|
||||
const errorAction = await sagaTester.waitFor(expectedAction.type);
|
||||
|
||||
expect(errorAction).toEqual(errorHandler.createClearingAction());
|
||||
});
|
||||
|
||||
it('dispatches an error', async () => {
|
||||
|
@ -95,11 +130,12 @@ describe(__filename, () => {
|
|||
.once()
|
||||
.returns(Promise.reject(error));
|
||||
|
||||
_fetchOtherAddonsByAuthors({ authors: [], slug: fakeAddon.slug });
|
||||
_fetchAddonsByAuthors({ authors: [], forAddonSlug: fakeAddon.slug });
|
||||
|
||||
const errorAction = errorHandler.createErrorAction(error);
|
||||
await sagaTester.waitFor(errorAction.type);
|
||||
expect(sagaTester.getCalledActions()[2]).toEqual(errorAction);
|
||||
const calledErrorAction = await sagaTester.waitFor(errorAction.type);
|
||||
|
||||
expect(calledErrorAction).toEqual(errorAction);
|
||||
});
|
||||
|
||||
it('handles no API results', async () => {
|
||||
|
@ -116,21 +152,23 @@ describe(__filename, () => {
|
|||
addonType: ADDON_TYPE_THEME,
|
||||
author: authors.join(','),
|
||||
exclude_addons: slug,
|
||||
page_size: OTHER_ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
page_size: ADDONS_BY_AUTHORS_PAGE_SIZE,
|
||||
sort: SEARCH_SORT_TRENDING,
|
||||
},
|
||||
})
|
||||
.once()
|
||||
.returns(Promise.resolve(createAddonsApiResult(addons)));
|
||||
|
||||
_fetchOtherAddonsByAuthors({ authors, slug });
|
||||
_fetchAddonsByAuthors({ authors, forAddonSlug: slug });
|
||||
|
||||
const expectedLoadAction = loadOtherAddonsByAuthors({ addons, slug });
|
||||
const expectedLoadAction = loadAddonsByAuthors({
|
||||
addons,
|
||||
forAddonSlug: slug,
|
||||
});
|
||||
|
||||
await sagaTester.waitFor(expectedLoadAction.type);
|
||||
const loadAction = await sagaTester.waitFor(expectedLoadAction.type);
|
||||
mockApi.verify();
|
||||
|
||||
const loadAction = sagaTester.getCalledActions()[2];
|
||||
expect(loadAction).toEqual(expectedLoadAction);
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче