Remove feedback form feature flags (#12753)

This commit is contained in:
William Durand 2024-01-08 16:52:45 +01:00 коммит произвёл GitHub
Родитель 9fb30f29a8
Коммит f96185168e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
24 изменённых файлов: 132 добавлений и 1968 удалений

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

@ -86,8 +86,6 @@ module.exports = {
'defaultLang',
'enableDevTools',
'enableFeatureVPNPromo',
'enableFeatureFeedbackForm',
'enableFeatureFeedbackFormLinks',
'enableRequestID',
'enableStrictMode',
'experiments',
@ -413,9 +411,6 @@ module.exports = {
enableFeatureVPNPromo: true,
enableFeatureFeedbackForm: true,
enableFeatureFeedbackFormLinks: true,
extensionWorkshopUrl: 'https://extensionworkshop.com',
// The withExperiment HOC relies on this config to enable/disable A/B

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

@ -99,30 +99,6 @@ export function getAddon(
return Promise.reject(new Error('Cannot check add-on status'));
}
export function hasAbuseReportPanelEnabled(
_mozAddonManager?: MozAddonManagerType = window.navigator.mozAddonManager,
): boolean {
if (_mozAddonManager || hasAddonManager()) {
return _mozAddonManager.abuseReportPanelEnabled || false;
}
return false;
}
export function reportAbuse(
addonId: string,
{ _mozAddonManager = window.navigator.mozAddonManager }: OptionalParams = {},
): Promise<boolean> {
if (
hasAbuseReportPanelEnabled(_mozAddonManager) &&
_mozAddonManager.reportAbuse
) {
return (
_mozAddonManager.reportAbuse && _mozAddonManager.reportAbuse(addonId)
);
}
return Promise.reject(new Error('Cannot report abuse via Firefox'));
}
type OptionalInstallParams = {
...OptionalParams,
_log?: typeof log,

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

@ -3,16 +3,13 @@ import invariant from 'invariant';
import * as React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import config from 'config';
import {
REVIEW_FLAG_REASON_BUG_SUPPORT,
REVIEW_FLAG_REASON_LANGUAGE,
REVIEW_FLAG_REASON_SPAM,
} from 'amo/constants';
import Link from 'amo/components/Link';
import FlagReview from 'amo/components/FlagReview';
import AuthenticateButton from 'amo/components/AuthenticateButton';
import { getCurrentUser } from 'amo/reducers/users';
import translate from 'amo/i18n/translate';
import ListItem from 'amo/components/ListItem';
@ -65,93 +62,46 @@ export class FlagReviewMenuBase extends React.Component<InternalProps> {
'A user cannot flag their own review.',
);
const enableFeatureFeedbackFormLinks = config.get(
'enableFeatureFeedbackFormLinks',
);
const disableItemsForUnauthenticatedUsers = enableFeatureFeedbackFormLinks
? !siteUser
: false;
let items;
if (!enableFeatureFeedbackFormLinks && !siteUser) {
items = [
<ListItem key="login-required">
<AuthenticateButton
noIcon
logInText={
isDeveloperReply
? i18n.gettext('Log in to flag this response')
: i18n.gettext('Log in to flag this review')
}
/>
</ListItem>,
];
} else {
items = [
<ListItem className="FlagReviewMenu-flag-spam-item" key="flag-spam">
const items = [
<ListItem className="FlagReviewMenu-flag-spam-item" key="flag-spam">
<FlagReview
reason={REVIEW_FLAG_REASON_SPAM}
review={review}
buttonText={i18n.gettext('Spam')}
wasFlaggedText={i18n.gettext('Flagged as spam')}
disabled={!siteUser}
disabledTitle={i18n.gettext('Login required')}
/>
</ListItem>,
// Only reviews (not developer responses) can be flagged as
// misplaced bug reports or support requests.
isDeveloperReply ? null : (
<ListItem
className="FlagReviewMenu-flag-bug-support-item"
key="flag-bug-support"
>
<FlagReview
reason={REVIEW_FLAG_REASON_SPAM}
reason={REVIEW_FLAG_REASON_BUG_SUPPORT}
review={review}
buttonText={
enableFeatureFeedbackFormLinks
? i18n.gettext('Spam')
: i18n.gettext('This is spam')
}
wasFlaggedText={i18n.gettext('Flagged as spam')}
disabled={disableItemsForUnauthenticatedUsers}
buttonText={i18n.gettext('Misplaced bug report or support request')}
wasFlaggedText={i18n.gettext(
`Flagged as misplaced bug report or support request`,
)}
disabled={!siteUser}
disabledTitle={i18n.gettext('Login required')}
/>
</ListItem>,
// Only reviews (not developer responses) can be flagged as
// misplaced bug reports or support requests.
isDeveloperReply ? null : (
<ListItem
className="FlagReviewMenu-flag-bug-support-item"
key="flag-bug-support"
>
<FlagReview
reason={REVIEW_FLAG_REASON_BUG_SUPPORT}
review={review}
buttonText={
enableFeatureFeedbackFormLinks
? i18n.gettext('Misplaced bug report or support request')
: i18n.gettext('This is a bug report or support request')
}
wasFlaggedText={
enableFeatureFeedbackFormLinks
? i18n.gettext(`Flagged as misplaced bug report or support
request`)
: i18n.gettext('Flagged as a bug report or support request')
}
disabled={disableItemsForUnauthenticatedUsers}
disabledTitle={i18n.gettext('Login required')}
/>
</ListItem>
),
<ListItem
className="FlagReviewMenu-flag-language-item"
key="flag-language"
>
{enableFeatureFeedbackFormLinks ? (
<Link to={`/feedback/review/${review.id}/`}>
{i18n.gettext(`Content that is illegal or that violates AMO's
content policies`)}
</Link>
) : (
<FlagReview
reason={REVIEW_FLAG_REASON_LANGUAGE}
review={review}
buttonText={i18n.gettext('This contains inappropriate language')}
wasFlaggedText={i18n.gettext(
'Flagged for inappropriate language',
)}
disabledTitle={i18n.gettext('Login required')}
/>
)}
</ListItem>,
];
}
</ListItem>
),
<ListItem
className="FlagReviewMenu-flag-language-item"
key="flag-language"
>
<Link to={`/feedback/review/${review.id}/`}>
{i18n.gettext(`Content that is illegal or that violates AMO's content
policies`)}
</Link>
</ListItem>,
];
return (
<TooltipMenu

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

@ -4,27 +4,12 @@ import invariant from 'invariant';
import * as React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import config from 'config';
import { hasAbuseReportPanelEnabled } from 'amo/addonManager';
import { ADDON_TYPE_EXTENSION, ADDON_TYPE_STATIC_THEME } from 'amo/constants';
import { withErrorHandler } from 'amo/errorHandler';
import type { ErrorHandlerType } from 'amo/types/errorHandler';
import translate from 'amo/i18n/translate';
import {
hideAddonAbuseReportUI,
initiateAddonAbuseReportViaFirefox,
sendAddonAbuseReport,
showAddonAbuseReportUI,
} from 'amo/reducers/abuse';
import { normalizeFileNameId, sanitizeHTML } from 'amo/utils';
import Button from 'amo/components/Button';
import DismissibleTextForm from 'amo/components/DismissibleTextForm';
import type { OnSubmitParams } from 'amo/components/DismissibleTextForm';
import type { AddonAbuseState } from 'amo/reducers/abuse';
import type { AppState } from 'amo/store';
import type { AddonType } from 'amo/types/addons';
import type { ElementEvent } from 'amo/types/dom';
import type { I18nType } from 'amo/types/i18n';
import type { DispatchFunc } from 'amo/types/redux';
@ -39,79 +24,19 @@ type PropsFromState = {|
loading: boolean,
|};
type DefaultProps = {|
_hasAbuseReportPanelEnabled: typeof hasAbuseReportPanelEnabled,
|};
type InternalProps = {|
...Props,
...PropsFromState,
...DefaultProps,
dispatch: DispatchFunc,
errorHandler: ErrorHandlerType,
i18n: I18nType,
|};
export class ReportAbuseButtonBase extends React.Component<InternalProps> {
static defaultProps: DefaultProps = {
_hasAbuseReportPanelEnabled: hasAbuseReportPanelEnabled,
};
dismissReportUI: () => void = () => {
const { addon, dispatch } = this.props;
dispatch(hideAddonAbuseReportUI({ addon }));
};
sendReport: (OnSubmitParams) => void = ({ text }: OnSubmitParams) => {
// The button isn't clickable if there is no content, but just in case:
// we verify there's a message to send.
invariant(text.trim().length, 'A report cannot be sent with no content.');
const { addon, dispatch, errorHandler } = this.props;
dispatch(
sendAddonAbuseReport({
addonId: addon.slug,
errorHandlerId: errorHandler.id,
message: text,
auth: true,
}),
);
};
onReportButtonClick: (event: ElementEvent) => Promise<void> = async (
event: ElementEvent,
) => {
const { _hasAbuseReportPanelEnabled, addon, dispatch } = this.props;
event.preventDefault();
if (
_hasAbuseReportPanelEnabled() &&
// The integrated abuse report panel currently supports only extensions
// and themes (e.g. we do only have abuse categories and related
// localized descriptions only for these two kind of addons), and so
// currently it is going to refuse to create an abuse report panel for
// langpacks, dictionaries and search tools.
[ADDON_TYPE_EXTENSION, ADDON_TYPE_STATIC_THEME].includes(addon.type)
) {
dispatch(initiateAddonAbuseReportViaFirefox({ addon }));
return;
}
dispatch(showAddonAbuseReportUI({ addon }));
};
render(): null | React.Node {
const { abuseReport, addon, errorHandler, i18n, loading } = this.props;
const { abuseReport, addon, i18n, loading } = this.props;
invariant(addon, 'An add-on is required');
const enableFeatureFeedbackFormLinks = config.get(
'enableFeatureFeedbackFormLinks',
);
if (abuseReport && abuseReport.message !== undefined) {
return (
<div className="ReportAbuseButton ReportAbuseButton--report-sent">
@ -123,52 +48,17 @@ export class ReportAbuseButtonBase extends React.Component<InternalProps> {
{i18n.gettext(`We have received your report. Thanks for letting us
know about your concerns with this add-on.`)}
</p>
{!enableFeatureFeedbackFormLinks && (
<p>
{i18n.gettext(`We can't respond to every abuse report but we'll
look into this issue.`)}
</p>
)}
</div>
);
}
const prefaceText = i18n.sprintf(
i18n.gettext(
`If you think this add-on violates
%(linkTagStart)sMozilla's add-on policies%(linkTagEnd)s or has
security or privacy issues, please report these issues to Mozilla using
this form.`,
),
{
linkTagStart:
'<a href="https://developer.mozilla.org/en-US/Add-ons/AMO/Policy/Reviews">',
linkTagEnd: '</a>',
},
);
const prompt = i18n.gettext('Report this add-on');
let reportButtonProps: Object = {
onClick: this.onReportButtonClick,
const reportButtonProps: Object = {
to: `/feedback/addon/${addon.slug}/`,
};
// When this feature flag is active, we link to the feedback form.
if (enableFeatureFeedbackFormLinks) {
reportButtonProps = {
to: `/feedback/addon/${addon.slug}/`,
};
}
/* eslint-disable react/no-danger */
return (
<div
className={makeClassName('ReportAbuseButton', {
'ReportAbuseButton--is-expanded':
abuseReport && abuseReport.uiVisible,
})}
>
<div className={makeClassName('ReportAbuseButton')}>
<div className="ReportAbuseButton--preview">
<Button
buttonType="neutral"
@ -177,41 +67,9 @@ export class ReportAbuseButtonBase extends React.Component<InternalProps> {
puffy
{...reportButtonProps}
>
{prompt}
{i18n.gettext('Report this add-on')}
</Button>
</div>
<div className="ReportAbuseButton--expanded">
<h3 className="ReportAbuseButton-header">{prompt}</h3>
<p
className="ReportAbuseButton-first-paragraph"
dangerouslySetInnerHTML={sanitizeHTML(prefaceText, ['a'])}
/>
<p>
{i18n.gettext(
`Please don't use this form to report bugs or request add-on
features; this report will be sent to Mozilla and not to the
add-on developer.`,
)}
</p>
{errorHandler.renderErrorIfPresent()}
<DismissibleTextForm
id={normalizeFileNameId(__filename)}
isSubmitting={loading}
onSubmit={this.sendReport}
submitButtonText={i18n.gettext('Send abuse report')}
submitButtonInProgressText={i18n.gettext('Sending abuse report')}
onDismiss={this.dismissReportUI}
dismissButtonText={i18n.gettext('Dismiss')}
placeholder={i18n.gettext(
'Explain how this add-on is violating our policies.',
)}
/>
</div>
</div>
);
/* eslint-enable react/no-danger */
@ -233,7 +91,6 @@ const mapStateToProps = (state: AppState, ownProps: Props): PropsFromState => {
const ReportAbuseButton: React.ComponentType<Props> = compose(
connect(mapStateToProps),
translate(),
withErrorHandler({ id: 'ReportAbuseButton' }),
)(ReportAbuseButtonBase);
export default ReportAbuseButton;

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

@ -15,32 +15,8 @@
.ReportAbuseButton--preview {
display: inline-block;
width: 100%;
.ReportAbuseButton--is-expanded & {
display: none;
}
}
.ReportAbuseButton--expanded {
display: none;
.ReportAbuseButton--is-expanded & {
display: block;
}
}
.ReportAbuseButton-show-more {
width: 100%;
}
.DismissibleTextForm-dismiss,
.DismissibleTextForm-submit {
flex-wrap: wrap;
font-size: $font-size-s;
padding: 0 10px;
text-align: center;
}
.DismissibleTextForm-delete-submit-buttons {
padding-left: 5px;
}

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

@ -3,24 +3,13 @@ import makeClassName from 'classnames';
import * as React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import config from 'config';
import {
hideUserAbuseReportUI,
sendUserAbuseReport,
showUserAbuseReportUI,
} from 'amo/reducers/userAbuseReports';
import { withErrorHandler } from 'amo/errorHandler';
import translate from 'amo/i18n/translate';
import { normalizeFileNameId, sanitizeHTML } from 'amo/utils';
import Button from 'amo/components/Button';
import DismissibleTextForm from 'amo/components/DismissibleTextForm';
import type { AppState } from 'amo/store';
import type { ErrorHandlerType } from 'amo/types/errorHandler';
import type { DispatchFunc } from 'amo/types/redux';
import type { I18nType } from 'amo/types/i18n';
import type { UserType } from 'amo/reducers/users';
import type { OnSubmitParams } from 'amo/components/DismissibleTextForm';
import './styles.scss';
@ -31,148 +20,29 @@ type Props = {|
type PropsFromState = {|
hasSubmitted: boolean,
isSubmitting: boolean,
uiVisible: boolean,
|};
type InternalProps = {|
...Props,
...PropsFromState,
dispatch: DispatchFunc,
errorHandler: ErrorHandlerType,
i18n: I18nType,
|};
export class ReportUserAbuseBase extends React.Component<InternalProps> {
hideReportUI: () => void = () => {
const { dispatch, user } = this.props;
if (user) {
dispatch(hideUserAbuseReportUI({ userId: user.id }));
}
};
sendReport: (reportData: OnSubmitParams) => void = (
reportData: OnSubmitParams,
) => {
const { dispatch, errorHandler, user } = this.props;
if (user) {
dispatch(
sendUserAbuseReport({
errorHandlerId: errorHandler.id,
message: reportData.text,
userId: user.id,
auth: true,
}),
);
}
};
showReportUI: () => void = () => {
const { dispatch, user } = this.props;
if (user) {
dispatch(showUserAbuseReportUI({ userId: user.id }));
}
};
render(): React.Node {
const {
className,
errorHandler,
hasSubmitted,
i18n,
isSubmitting,
uiVisible,
user,
} = this.props;
const { className, hasSubmitted, i18n, user } = this.props;
let reportButtonProps: Object = {
onClick: this.showReportUI,
};
const enableFeatureFeedbackFormLinks = config.get(
'enableFeatureFeedbackFormLinks',
);
// When this feature flag is active, we link to the feedback form.
if (user && enableFeatureFeedbackFormLinks) {
let reportButtonProps: Object = {};
if (user) {
reportButtonProps = {
to: `/feedback/user/${user.id}/`,
};
}
return (
<div
className={makeClassName('ReportUserAbuse', className, {
'ReportUserAbuse--is-expanded': uiVisible,
})}
>
{errorHandler.renderErrorIfPresent()}
{!uiVisible && !hasSubmitted && (
<Button
buttonType="neutral"
className="ReportUserAbuse-show-more"
disabled={!user}
puffy
{...reportButtonProps}
>
{i18n.gettext('Report this user')}
</Button>
)}
{!hasSubmitted && (
<div className="ReportUserAbuse-form">
<h2 className="ReportUserAbuse-header">
{i18n.gettext('Report this user')}
</h2>
<p
/* eslint-disable react/no-danger */
dangerouslySetInnerHTML={sanitizeHTML(
i18n.sprintf(
i18n.gettext(
`If you think this user is violating
%(linkTagStart)sMozilla's Add-on Policies%(linkTagEnd)s,
please report this user to Mozilla.`,
),
{
linkTagStart:
'<a href="https://developer.mozilla.org/en-US/Add-ons/AMO/Policy/Reviews">',
linkTagEnd: '</a>',
},
),
['a'],
)}
/* eslint-enable react/no-danger */
/>
<p>
{i18n.gettext(
`Please don't use this form to report bugs or contact this
user; your report will only be sent to Mozilla and not
to this user.`,
)}
</p>
<DismissibleTextForm
id={`${normalizeFileNameId(__filename)}-${String(
user && user.id,
)}`}
isSubmitting={isSubmitting}
onDismiss={this.hideReportUI}
onSubmit={this.sendReport}
placeholder={i18n.gettext(
'Explain how this user is violating our policies.',
)}
submitButtonText={i18n.gettext('Send abuse report')}
submitButtonInProgressText={i18n.gettext('Sending abuse report')}
/>
</div>
)}
{hasSubmitted && (
<div className={makeClassName('ReportUserAbuse', className)}>
{hasSubmitted ? (
<div className="ReportUserAbuse--report-sent">
<h3 className="ReportUserAbuse-header">
{i18n.gettext('You reported this user')}
@ -182,14 +52,17 @@ export class ReportUserAbuseBase extends React.Component<InternalProps> {
{i18n.gettext(`We have received your report. Thanks for letting
us know about your concerns with this user.`)}
</p>
{!enableFeatureFeedbackFormLinks && (
<p>
{i18n.gettext(`We can't respond to every abuse report but we'll
look into this issue.`)}
</p>
)}
</div>
) : (
<Button
buttonType="neutral"
className="ReportUserAbuse-show-more"
disabled={!user}
puffy
{...reportButtonProps}
>
{i18n.gettext('Report this user')}
</Button>
)}
</div>
);
@ -204,15 +77,12 @@ const mapStateToProps = (state: AppState, ownProps: Props): PropsFromState => {
return {
hasSubmitted: abuseReport.hasSubmitted || false,
isSubmitting: abuseReport.isSubmitting || false,
uiVisible: abuseReport.uiVisible || false,
};
};
const ReportUserAbuse: React.ComponentType<Props> = compose(
connect(mapStateToProps),
translate(),
withErrorHandler({ id: 'ReportUserAbuse' }),
)(ReportUserAbuseBase);
export default ReportUserAbuse;

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

@ -5,11 +5,3 @@
.ReportUserAbuse p:first-of-type {
margin-top: 6px;
}
.ReportUserAbuse-form {
display: none;
.ReportUserAbuse--is-expanded & {
display: block;
}
}

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

@ -51,25 +51,21 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
<Route exact path="/:lang/about" component={About} />
{/* TODO: Post launch update this URL and redirect see #3374/ */}
<Route exact path="/:lang/review_guide" component={ReviewGuide} />
<Route
exact
path="/:lang/:application(firefox|android)/"
component={Home}
/>
<Route
exact
path="/:lang/:application(firefox|android)/addon/:slug/"
component={Addon}
/>
<Route
exact
path="/:lang/:application(firefox|android)/blocked-addon/:guid/:versionId?/"
component={Block}
/>
<Route
exact
path="/:lang/:application(firefox|android)/addon/:addonSlug/reviews/:reviewId"
@ -80,7 +76,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
path="/:lang/:application(firefox|android)/addon/:addonSlug/reviews/"
component={AddonReviewList}
/>
<Route
exact
path="/:lang/:application(firefox|android)/addon/:slug/privacy/"
@ -88,7 +83,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
<AddonInfo {...props} infoType={ADDON_INFO_TYPE_PRIVACY_POLICY} />
)}
/>
<Route
exact
path="/:lang/:application(firefox|android)/addon/:slug/eula/"
@ -96,7 +90,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
<AddonInfo {...props} infoType={ADDON_INFO_TYPE_EULA} />
)}
/>
<Route
exact
path="/:lang/:application(firefox|android)/addon/:slug/license/"
@ -104,13 +97,11 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
<AddonInfo {...props} infoType={ADDON_INFO_TYPE_CUSTOM_LICENSE} />
)}
/>
<Route
exact
path="/:lang/:application(firefox|android)/addon/:slug/versions/"
component={AddonVersions}
/>
<Route
exact
path="/:lang/:application(firefox|android)/users/edit"
@ -126,7 +117,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
path="/:lang/:application(firefox|android)/user/:userId/"
component={UserProfile}
/>
<Route
exact
path="/:lang/:application(firefox|android)/collections/:userId/:slug/"
@ -147,7 +137,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
path="/:lang/:application(firefox|android)/collections/:userId/:slug/edit/"
component={CollectionEdit}
/>
{/* Only show category pages for both extensions and themes on Desktop. For
Android, we only allow category pages for extensions since Firefox for
Android doesn't support themes. */}
@ -171,41 +160,39 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
path="/:lang/:application(firefox)/:visibleAddonType(themes)/category/:categorySlug/"
component={CategoryPage}
/>
<Route
exact
path="/:lang/:application(firefox|android)/tag/:tag/"
component={TagPage}
/>
{config.get('enableFeatureFeedbackForm') && [
<Route
key="addon-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/addon/:addonIdentifier/"
component={AddonFeedback}
/>,
<Route
key="collection-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/collection/:authorId/:collectionSlug/"
component={CollectionFeedback}
/>,
<Route
key="user-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/user/:userId/"
component={UserFeedback}
/>,
<Route
key="rating-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/review/:ratingId/"
component={RatingFeedback}
/>,
]}
{/* See: https://github.com/mozilla/addons-frontend/issues/5150 */}
<Route
key="addon-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/addon/:addonIdentifier/"
component={AddonFeedback}
/>
,
<Route
key="collection-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/collection/:authorId/:collectionSlug/"
component={CollectionFeedback}
/>
,
<Route
key="user-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/user/:userId/"
component={UserFeedback}
/>
,
<Route
key="rating-feedback"
exact
path="/:lang/:application(firefox|android)/feedback/review/:ratingId/"
component={RatingFeedback}
/>
,{/* See: https://github.com/mozilla/addons-frontend/issues/5150 */}
<Route
exact
path="/:lang/android/language-tools/"
@ -226,7 +213,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
path="/:lang/:application(firefox|android)/search/"
component={SearchPage}
/>
<Route
exact
path="/:lang/:application(firefox|android)/401/"
@ -249,7 +235,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
path="/:lang/:application(firefox|android)/500/"
component={_config.get('isDevelopment') ? ServerErrorPage : NotFoundPage}
/>
<Route
exact
path="/:lang/:application(firefox|android)/simulate-async-error/"
@ -268,7 +253,6 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
</Page>
)}
/>
{/* Only show category pages for both extensions and themes on Desktop. For
Android, we only allow category pages for extensions since Firefox for
Android doesn't support themes. */}
@ -282,13 +266,11 @@ const Routes = ({ _config = config }: Props = {}): React.Node => (
path="/:lang/:application(firefox)/:visibleAddonType(themes)/"
component={LandingPage}
/>
<Route
exact
path="/:lang/:application(firefox|android)/users/unsubscribe/:token/:hash/:notificationName/"
component={UsersUnsubscribe}
/>
<Route component={NotFoundPage} />
</Switch>
);

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

@ -5,7 +5,6 @@ import * as React from 'react';
import { Helmet } from 'react-helmet';
import { connect } from 'react-redux';
import { compose } from 'redux';
import config from 'config';
import AddonsCard from 'amo/components/AddonsCard';
import Button from 'amo/components/Button';
@ -409,10 +408,6 @@ export class CollectionBase extends React.Component<InternalProps> {
}
renderAbuseReportButton(): null | React.Node {
if (!config.get('enableFeatureFeedbackFormLinks')) {
return null;
}
const { collection, i18n, isOwner, isReported } = this.props;
if (!collection || isOwner) {

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

@ -1,22 +1,13 @@
/* @flow */
import invariant from 'invariant';
import type { AddonType } from 'amo/types/addons';
import type { AbuseReporter, ReportAddonResponse } from 'amo/api/abuse';
export const ABORT_ABUSE_REPORT: 'ABORT_ABUSE_REPORT' = 'ABORT_ABUSE_REPORT';
export const HIDE_ADDON_ABUSE_REPORT_UI: 'HIDE_ADDON_ABUSE_REPORT_UI' =
'HIDE_ADDON_ABUSE_REPORT_UI';
export const LOAD_ADDON_ABUSE_REPORT: 'LOAD_ADDON_ABUSE_REPORT' =
'LOAD_ADDON_ABUSE_REPORT';
export const SEND_ADDON_ABUSE_REPORT: 'SEND_ADDON_ABUSE_REPORT' =
'SEND_ADDON_ABUSE_REPORT';
export const SHOW_ADDON_ABUSE_REPORT_UI: 'SHOW_ADDON_ABUSE_REPORT_UI' =
'SHOW_ADDON_ABUSE_REPORT_UI';
export const INITIATE_ADDON_ABUSE_REPORT_VIA_FIREFOX: 'INITIATE_ADDON_ABUSE_REPORT_VIA_FIREFOX' =
'INITIATE_ADDON_ABUSE_REPORT_VIA_FIREFOX';
export const FINISH_ADDON_ABUSE_REPORT_VIA_FIREFOX: 'FINISH_ADDON_ABUSE_REPORT_VIA_FIREFOX' =
'FINISH_ADDON_ABUSE_REPORT_VIA_FIREFOX';
type AbortAbuseReportParams = {|
addonId: string,
@ -42,7 +33,6 @@ export type AddonAbuseState = {|
buttonEnabled?: boolean,
message: string | null,
reporter: AbuseReporter | null,
uiVisible?: boolean,
|};
export type AbuseState = {|
@ -57,24 +47,6 @@ export const initialState: AbuseState = {
loading: false,
};
type HideAddonAbuseReportUIParams = {| addon: AddonType |};
type HideAddonAbuseReportUIAction = {|
type: typeof HIDE_ADDON_ABUSE_REPORT_UI,
payload: HideAddonAbuseReportUIParams,
|};
export function hideAddonAbuseReportUI({
addon,
}: HideAddonAbuseReportUIParams): HideAddonAbuseReportUIAction {
invariant(addon, 'addon is required');
return {
type: HIDE_ADDON_ABUSE_REPORT_UI,
payload: { addon },
};
}
type LoadAddonAbuseReportAction = {|
type: typeof LOAD_ADDON_ABUSE_REPORT,
payload: ReportAddonResponse,
@ -156,59 +128,7 @@ export function sendAddonAbuseReport({
};
}
type ShowAddonAbuseReportUIParams = {| addon: AddonType |};
type ShowAddonAbuseReportUIAction = {|
type: typeof SHOW_ADDON_ABUSE_REPORT_UI,
payload: ShowAddonAbuseReportUIParams,
|};
export function showAddonAbuseReportUI({
addon,
}: ShowAddonAbuseReportUIParams): ShowAddonAbuseReportUIAction {
invariant(addon, 'addon is required');
return {
type: SHOW_ADDON_ABUSE_REPORT_UI,
payload: { addon },
};
}
type InitiateAddonAbuseReportViaFirefoxParams = {| addon: AddonType |};
export type InitiateAddonAbuseReportViaFirefoxAction = {|
type: typeof INITIATE_ADDON_ABUSE_REPORT_VIA_FIREFOX,
payload: InitiateAddonAbuseReportViaFirefoxParams,
|};
export function initiateAddonAbuseReportViaFirefox({
addon,
}: InitiateAddonAbuseReportViaFirefoxParams): InitiateAddonAbuseReportViaFirefoxAction {
invariant(addon, 'addon is required');
return {
type: INITIATE_ADDON_ABUSE_REPORT_VIA_FIREFOX,
payload: { addon },
};
}
export type FinishAddonAbuseReportViaFirefoxAction = {|
type: typeof FINISH_ADDON_ABUSE_REPORT_VIA_FIREFOX,
|};
export function finishAddonAbuseReportViaFirefox(): FinishAddonAbuseReportViaFirefoxAction {
return {
type: FINISH_ADDON_ABUSE_REPORT_VIA_FIREFOX,
};
}
type Action =
| FinishAddonAbuseReportViaFirefoxAction
| HideAddonAbuseReportUIAction
| InitiateAddonAbuseReportViaFirefoxAction
| LoadAddonAbuseReportAction
| SendAddonAbuseReportAction
| ShowAddonAbuseReportUIAction;
type Action = LoadAddonAbuseReportAction | SendAddonAbuseReportAction;
export default function abuseReducer(
// eslint-disable-next-line default-param-last
@ -222,43 +142,19 @@ export default function abuseReducer(
loading: false,
};
}
case HIDE_ADDON_ABUSE_REPORT_UI: {
const { addon } = action.payload;
return {
...state,
byGUID: {
...state.byGUID,
[addon.guid]: { ...state.byGUID[addon.guid], uiVisible: false },
},
};
}
case LOAD_ADDON_ABUSE_REPORT: {
const { addon, message, reporter } = action.payload;
return {
...state,
byGUID: {
...state.byGUID,
[addon.guid]: { message, reporter, uiVisible: false },
[addon.guid]: { message, reporter },
},
loading: false,
};
}
case SEND_ADDON_ABUSE_REPORT:
return { ...state, loading: true };
case SHOW_ADDON_ABUSE_REPORT_UI: {
const { addon } = action.payload;
return {
...state,
byGUID: {
...state.byGUID,
[addon.guid]: { ...state.byGUID[addon.guid], uiVisible: true },
},
};
}
case FINISH_ADDON_ABUSE_REPORT_VIA_FIREFOX:
return { ...state, loading: false };
default:
return state;
}

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

@ -6,14 +6,10 @@ import type { UserId } from 'amo/reducers/users';
export const ABORT_USER_ABUSE_REPORT: 'ABORT_USER_ABUSE_REPORT' =
'ABORT_USER_ABUSE_REPORT';
export const HIDE_USER_ABUSE_REPORT_UI: 'HIDE_USER_ABUSE_REPORT_UI' =
'HIDE_USER_ABUSE_REPORT_UI';
export const LOAD_USER_ABUSE_REPORT: 'LOAD_USER_ABUSE_REPORT' =
'LOAD_USER_ABUSE_REPORT';
export const SEND_USER_ABUSE_REPORT: 'SEND_USER_ABUSE_REPORT' =
'SEND_USER_ABUSE_REPORT';
export const SHOW_USER_ABUSE_REPORT_UI: 'SHOW_USER_ABUSE_REPORT_UI' =
'SHOW_USER_ABUSE_REPORT_UI';
type AbortUserAbuseReportParams = {|
userId: UserId,
@ -35,26 +31,6 @@ export function abortUserAbuseReport({
};
}
type HideUserAbuseReportUIParams = {|
userId: UserId,
|};
type HideUserAbuseReportUIAction = {|
type: typeof HIDE_USER_ABUSE_REPORT_UI,
payload: HideUserAbuseReportUIParams,
|};
export function hideUserAbuseReportUI({
userId,
}: HideUserAbuseReportUIParams): HideUserAbuseReportUIAction {
invariant(userId, 'userId is required');
return {
type: HIDE_USER_ABUSE_REPORT_UI,
payload: { userId },
};
}
type LoadUserAbuseReportParams = {|
message: string,
reporter: AbuseReporter,
@ -128,32 +104,11 @@ export function sendUserAbuseReport({
};
}
type ShowUserAbuseReportUIParams = {|
userId: UserId,
|};
type ShowUserAbuseReportUIActions = {|
type: typeof SHOW_USER_ABUSE_REPORT_UI,
payload: ShowUserAbuseReportUIParams,
|};
export function showUserAbuseReportUI({
userId,
}: ShowUserAbuseReportUIParams): ShowUserAbuseReportUIActions {
invariant(userId, 'userId is required');
return {
type: SHOW_USER_ABUSE_REPORT_UI,
payload: { userId },
};
}
export type UserAbuseReportState = {|
hasSubmitted?: boolean,
isSubmitting: boolean,
message?: string,
reportedByUserId: number | null,
uiVisible?: boolean,
|};
export type UserAbuseReportsState = {|
@ -164,9 +119,7 @@ export type UserAbuseReportsState = {|
export type UserAbuseReportActionType =
| AbortUserAbuseReportAction
| HideUserAbuseReportUIAction
| SendUserAbuseReportAction
| ShowUserAbuseReportUIActions
| LoadUserAbuseReportAction;
export const initialState: UserAbuseReportsState = {
@ -190,22 +143,10 @@ export default function userAbuseReportReducer(
...state.byUserId[userId],
hasSubmitted: false,
isSubmitting: false,
uiVisible: false,
},
},
};
}
case HIDE_USER_ABUSE_REPORT_UI: {
const { userId } = action.payload;
return {
...state,
byUserId: {
...state.byUserId,
[userId]: { ...state.byUserId[userId], uiVisible: false },
},
};
}
case LOAD_USER_ABUSE_REPORT: {
const { message, reportedByUserId, userId } = action.payload;
return {
@ -217,7 +158,6 @@ export default function userAbuseReportReducer(
reportedByUserId,
hasSubmitted: true,
isSubmitting: false,
uiVisible: false,
},
},
};
@ -233,17 +173,6 @@ export default function userAbuseReportReducer(
},
};
}
case SHOW_USER_ABUSE_REPORT_UI: {
const { userId } = action.payload;
return {
...state,
byUserId: {
...state.byUserId,
[userId]: { ...state.byUserId[userId], uiVisible: true },
},
};
}
default:
return state;
}

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

@ -1,22 +1,16 @@
/* @flow */
import { call, put, select, takeLatest } from 'redux-saga/effects';
import { reportAbuse } from 'amo/addonManager';
import { reportAddon as reportAddonApi } from 'amo/api/abuse';
import log from 'amo/logger';
import {
INITIATE_ADDON_ABUSE_REPORT_VIA_FIREFOX,
SEND_ADDON_ABUSE_REPORT,
abortAbuseReport,
finishAddonAbuseReportViaFirefox,
loadAddonAbuseReport,
} from 'amo/reducers/abuse';
import { createErrorHandler, getState } from 'amo/sagas/utils';
import type { ReportAddonParams } from 'amo/api/abuse';
import type {
InitiateAddonAbuseReportViaFirefoxAction,
SendAddonAbuseReportAction,
} from 'amo/reducers/abuse';
import type { SendAddonAbuseReportAction } from 'amo/reducers/abuse';
import type { Saga } from 'amo/types/sagas';
export function* reportAddon({
@ -72,35 +66,6 @@ export function* reportAddon({
}
}
export function* reportAddonViaFirefox({
payload: { addon },
}: InitiateAddonAbuseReportViaFirefoxAction): Saga {
try {
const abuseReported = yield reportAbuse(addon.guid);
if (abuseReported) {
yield put(
loadAddonAbuseReport({
addon: { guid: addon.guid, id: addon.id, slug: addon.slug },
message: '',
reporter: null,
reporter_email: null,
reporter_name: null,
reason: null,
location: null,
addon_version: null,
}),
);
}
} catch (error) {
log.warn(`Reporting add-on for abuse via firefox failed: ${error}`);
}
yield put(finishAddonAbuseReportViaFirefox());
}
export default function* abuseSaga(): Saga {
yield takeLatest(
INITIATE_ADDON_ABUSE_REPORT_VIA_FIREFOX,
reportAddonViaFirefox,
);
yield takeLatest(SEND_ADDON_ABUSE_REPORT, reportAddon);
}

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

@ -3,7 +3,6 @@ import { createEvent, fireEvent, waitFor } from '@testing-library/react';
import defaultUserEvent, {
PointerEventsCheckLevel,
} from '@testing-library/user-event';
import config from 'config';
import {
SAVED_RATING,
@ -13,26 +12,19 @@ import {
cancelDeleteAddonReview,
createInternalReview,
deleteAddonReview,
flagReview,
flashReviewMessage,
hideEditReviewForm,
hideFlashedReviewMessage,
hideReplyToReviewForm,
sendReplyToReview,
setReview,
setReviewWasFlagged,
showEditReviewForm,
showReplyToReviewForm,
updateAddonReview,
} from 'amo/actions/reviews';
import AddonReviewCard from 'amo/components/AddonReviewCard';
import { extractId as addonReviewManagerExtractId } from 'amo/components/AddonReviewManager';
import {
ALL_SUPER_POWERS,
REVIEW_FLAG_REASON_BUG_SUPPORT,
REVIEW_FLAG_REASON_LANGUAGE,
REVIEW_FLAG_REASON_SPAM,
} from 'amo/constants';
import { ALL_SUPER_POWERS } from 'amo/constants';
import { reviewListURL } from 'amo/reducers/reviews';
import { logOutUser } from 'amo/reducers/users';
import {
@ -43,14 +35,11 @@ import {
fakeAddon,
fakeI18n,
fakeReview,
getMockConfig,
render as defaultRender,
screen,
within,
} from 'tests/unit/helpers';
jest.mock('config');
describe(__filename, () => {
let i18n;
let store;
@ -72,13 +61,6 @@ describe(__filename, () => {
// pointer events not being available.
pointerEventsCheck: PointerEventsCheckLevel.Never,
});
const fakeConfig = getMockConfig({
enableFeatureFeedbackFormLinks: false,
});
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
});
afterEach(() => {
@ -272,15 +254,6 @@ describe(__filename, () => {
});
};
const openFlagMenu = async ({ isReply = false } = {}) =>
userEvent.click(
screen.getByRole('button', {
description: isReply
? 'Flag this developer response'
: 'Flag this review',
}),
);
const clickDeleteRating = async () =>
userEvent.click(screen.getByRole('button', { name: 'Delete rating' }));
@ -1384,255 +1357,6 @@ describe(__filename, () => {
});
});
describe('Tests for FlagReview and FlagReviewMenu', () => {
const getErrorHandlerId = (reviewId) => `FlagReview-${reviewId}`;
it.each([
[
REVIEW_FLAG_REASON_BUG_SUPPORT,
'This is a bug report or support request',
'Flagged as a bug report or support request',
],
[
REVIEW_FLAG_REASON_LANGUAGE,
'This contains inappropriate language',
'Flagged for inappropriate language',
],
[REVIEW_FLAG_REASON_SPAM, 'This is spam', 'Flagged as spam'],
])('can flag a review for %s', async (reason, prompt, postText) => {
const dispatch = jest.spyOn(store, 'dispatch');
const review = createReviewAndSignInAsUnrelatedUser();
render({ review });
await openFlagMenu();
const button = screen.getByRole('button', {
name: prompt,
});
const clickEvent = createEvent.click(button);
const preventDefaultWatcher = jest.spyOn(clickEvent, 'preventDefault');
fireEvent(button, clickEvent);
expect(preventDefaultWatcher).toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith(
flagReview({
errorHandlerId: getErrorHandlerId(review.id),
reason,
reviewId: review.id,
}),
);
store.dispatch(setReviewWasFlagged({ reason, reviewId: review.id }));
expect(await screen.findByText(postText)).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Flagged' }),
).toBeInTheDocument();
});
it('renders loading text while in progress', async () => {
const review = createReviewAndSignInAsUnrelatedUser();
render({ review });
await openFlagMenu();
await userEvent.click(
screen.getByRole('button', {
name: 'This is a bug report or support request',
}),
);
expect(
within(screen.getByRole('tooltip')).getByRole('alert'),
).toBeInTheDocument();
});
it('renders an error', async () => {
const message = 'Some error message';
const review = createReviewAndSignInAsUnrelatedUser();
render({ review });
await openFlagMenu();
createFailedErrorHandler({
id: getErrorHandlerId(review.id),
message,
store,
});
// A message is displayed for each instance of FlagReview.
await waitFor(() => expect(screen.getAllByText(message)).toHaveLength(3));
// It should still display a button so they can try again.
expect(
screen.getByRole('button', {
name: 'This is a bug report or support request',
}),
).toBeInTheDocument();
});
describe('Tests for FlagReviewMenu', () => {
it('can be configured with an openerClass', () => {
const review = createReviewAndSignInAsUnrelatedUser();
render({ review });
// AddonReviewCard passes 'AddonReviewCard-control' as the openerClass to
// FlagReviewMenu.
const flagButton = screen.getByRole('button', {
description: 'Flag this review',
});
expect(flagButton).toHaveClass('AddonReviewCard-control');
// This tests the `className` prop of TooltipMenu.
expect(flagButton).toHaveClass('FlagReviewMenu-menu');
});
it('requires you to be signed in', async () => {
render({ review: _setReview() });
await openFlagMenu();
// Only the button item should be rendered.
expect(
screen.queryByRole('button', {
name: 'This is a bug report or support request',
}),
).not.toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'Log in to flag this review' }),
).toBeInTheDocument();
});
it('shows the menu for signed-in users', async () => {
dispatchSignInActionsWithStore({ store, userId: 999 });
render({ review: _setReview() });
await openFlagMenu();
expect(
screen.getByRole('button', { name: 'This is spam' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', {
name: 'This is a bug report or support request',
}),
).toBeInTheDocument();
expect(
screen.getByRole('button', {
name: 'This contains inappropriate language',
}),
).toBeInTheDocument();
});
describe('when enableFeatureFeedbackFormLinks is enabled', () => {
beforeEach(() => {
const fakeConfig = getMockConfig({
enableFeatureFeedbackFormLinks: true,
});
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
});
it('shows the menu for signed-out users', async () => {
render({ review: _setReview() });
await openFlagMenu();
expect(
screen.queryByRole('link', { name: 'Log in to flag this review' }),
).not.toBeInTheDocument();
expect(
screen.getByRole('button', {
name: 'Spam',
}),
).toBeDisabled();
expect(
screen.getByRole('button', {
name: 'Spam',
}),
).toHaveAttribute('title', 'Login required');
expect(
screen.getByRole('button', {
name: 'Misplaced bug report or support request',
}),
).toBeDisabled();
expect(
screen.getByRole('button', {
name: 'Misplaced bug report or support request',
}),
).toHaveAttribute('title', 'Login required');
});
it('shows the menu for signed-in users', async () => {
dispatchSignInActionsWithStore({ store, userId: 999 });
render({ review: _setReview() });
await openFlagMenu();
expect(
screen.getByRole('button', {
name: 'Spam',
}),
).not.toBeDisabled();
expect(
screen.getByRole('button', {
name: 'Spam',
}),
).not.toHaveAttribute('title');
expect(
screen.getByRole('button', {
name: 'Misplaced bug report or support request',
}),
).not.toBeDisabled();
expect(
screen.getByRole('button', {
name: 'Misplaced bug report or support request',
}),
).not.toHaveAttribute('title');
});
});
it('prompts you to flag a developer response after login', async () => {
renderNestedReply();
await openFlagMenu({ isReply: true });
expect(
screen.getByRole('link', { name: 'Log in to flag this response' }),
).toBeInTheDocument();
});
it('does not prompt you to flag a response as a bug/support', async () => {
dispatchSignInActionsWithStore({ store, userId: 999 });
renderNestedReply();
await openFlagMenu({ isReply: true });
expect(
screen.getByRole('button', { name: 'This is spam' }),
).toBeInTheDocument();
expect(
screen.queryByRole('button', {
name: 'This is a bug report or support request',
}),
).not.toBeInTheDocument();
});
it('does not change Flag prompt for other view state changes', () => {
const review = createReviewAndSignInAsUnrelatedUser();
// This initializes the flag view state which was triggering a bug.
store.dispatch(showReplyToReviewForm({ reviewId: review.id }));
render({ review });
expect(
screen.getByRole('button', { description: 'Flag this review' }),
).toHaveTextContent('Flag');
});
});
});
describe('Tests for UserReview', () => {
it('renders LoadingText without a review', () => {
render();

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

@ -1,7 +1,5 @@
import * as React from 'react';
import { createEvent, fireEvent } from '@testing-library/react';
import defaultUserEvent from '@testing-library/user-event';
import config from 'config';
import { createApiError } from 'amo/api';
import {
@ -22,14 +20,8 @@ import {
setReview,
updateAddonReview,
} from 'amo/actions/reviews';
import { hasAbuseReportPanelEnabled } from 'amo/addonManager';
import RatingManager from 'amo/components/RatingManager';
import {
SEND_ADDON_ABUSE_REPORT,
initiateAddonAbuseReportViaFirefox,
loadAddonAbuseReport,
sendAddonAbuseReport,
} from 'amo/reducers/abuse';
import { loadAddonAbuseReport } from 'amo/reducers/abuse';
import {
createFailedErrorHandler,
createFakeAddonAbuseReport,
@ -41,18 +33,10 @@ import {
fakeAddon,
fakeReview,
fakeVersion,
getMockConfig,
render as defaultRender,
screen,
} from 'tests/unit/helpers';
// Default the availability of the Firefox Report Abuse API to false.
jest.mock('amo/addonManager', () => ({
hasAbuseReportPanelEnabled: jest.fn().mockReturnValue(false),
}));
jest.mock('config');
// We need to mock validAddonTypes in a test.
const mockValidAddonTypesGetter = jest.fn();
jest.mock('amo/constants', () => ({
@ -73,11 +57,6 @@ describe(__filename, () => {
beforeEach(() => {
store = dispatchClientMetadata().store;
userEvent = defaultUserEvent.setup({ delay: null });
const fakeConfig = getMockConfig({ enableFeatureFeedbackFormLinks: false });
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
});
afterEach(() => {
@ -531,136 +510,6 @@ describe(__filename, () => {
});
describe('Tests for ReportAbuseButton', () => {
it('allows a user to report an add-on for abuse', async () => {
const dispatch = jest.spyOn(store, 'dispatch');
const message = 'This add-on is abusive.';
render();
// Renders with just the Report Abuse button visible.
expect(screen.getByClassName('ReportAbuseButton')).not.toHaveClass(
'ReportAbuseButton--is-expanded',
);
const button = screen.getByRole('button', {
name: 'Report this add-on',
});
const clickEvent = createEvent.click(button);
const preventDefaultWatcher = jest.spyOn(clickEvent, 'preventDefault');
fireEvent(button, clickEvent);
expect(preventDefaultWatcher).toHaveBeenCalled();
await userEvent.type(
screen.getByPlaceholderText(
'Explain how this add-on is violating our policies.',
),
message,
);
await userEvent.click(
screen.getByRole('button', { name: 'Send abuse report' }),
);
expect(
screen.getByRole('button', { name: 'Sending abuse report' }),
).toHaveClass('Button--disabled');
expect(dispatch).toHaveBeenCalledWith(
sendAddonAbuseReport({
addonId: fakeAddon.slug,
errorHandlerId: 'ReportAbuseButton',
message,
auth: true,
}),
);
await userEvent.click(screen.getByRole('button', { name: 'Dismiss' }));
// Dismiss should have been ignored.
expect(screen.getByClassName('ReportAbuseButton')).toHaveClass(
'ReportAbuseButton--is-expanded',
);
});
it('hides the form when Dismiss is clicked', async () => {
render();
await userEvent.click(
screen.getByRole('button', { name: 'Report this add-on' }),
);
expect(screen.getByClassName('ReportAbuseButton')).toHaveClass(
'ReportAbuseButton--is-expanded',
);
await userEvent.click(screen.getByRole('button', { name: 'Dismiss' }));
expect(screen.getByClassName('ReportAbuseButton')).not.toHaveClass(
'ReportAbuseButton--is-expanded',
);
});
it.each([ADDON_TYPE_EXTENSION, ADDON_TYPE_STATIC_THEME])(
'initiates an abuse report via Firefox when the "report" button is clicked if supported and add-on type is %s',
(addonType) => {
hasAbuseReportPanelEnabled.mockImplementation(() => {
return true;
});
const addon = createInternalAddonWithLang({
...fakeAddon,
type: addonType,
});
const dispatch = jest.spyOn(store, 'dispatch');
render({ addon });
const button = screen.getByRole('button', {
name: 'Report this add-on',
});
const clickEvent = createEvent.click(button);
const preventDefaultWatcher = jest.spyOn(clickEvent, 'preventDefault');
fireEvent(button, clickEvent);
expect(preventDefaultWatcher).toHaveBeenCalled();
expect(screen.getByClassName('ReportAbuseButton')).not.toHaveClass(
'ReportAbuseButton--is-expanded',
);
expect(dispatch).toHaveBeenCalledWith(
initiateAddonAbuseReportViaFirefox({ addon }),
);
},
);
it.each([ADDON_TYPE_DICT])(
'does not initiate an abuse report via Firefox when add-on type is %s',
async (addonType) => {
hasAbuseReportPanelEnabled.mockImplementation(() => {
return true;
});
const addon = createInternalAddonWithLang({
...fakeAddon,
type: addonType,
});
const dispatch = jest.spyOn(store, 'dispatch');
render({ addon });
await userEvent.click(
screen.getByRole('button', {
name: 'Report this add-on',
}),
);
expect(screen.getByClassName('ReportAbuseButton')).toHaveClass(
'ReportAbuseButton--is-expanded',
);
expect(dispatch).not.toHaveBeenCalledWith(
initiateAddonAbuseReportViaFirefox({ addon }),
);
},
);
it('does not render an abuse button for a langpack', () => {
const addon = createInternalAddonWithLang({
...fakeAddon,
@ -674,136 +523,6 @@ describe(__filename, () => {
).not.toBeInTheDocument();
});
it('shows a success message and hides the button if report was sent', () => {
const addon = fakeAddon;
const abuseResponse = createFakeAddonAbuseReport({
addon,
message: 'Seriously, where is my money?!',
});
store.dispatch(loadAddonAbuseReport(abuseResponse));
render({ addon });
expect(
screen.getByRole('heading', {
name: 'You reported this add-on',
}),
).toBeInTheDocument();
expect(
screen.getByText(/^We can't respond to every abuse report/),
).toBeInTheDocument();
expect(
screen.queryByRole('button', {
name: 'Report this add-on',
}),
).not.toBeInTheDocument();
});
it('shows a success message and hides the button if report via Firefox was dispatched', () => {
const addon = fakeAddon;
store.dispatch(
loadAddonAbuseReport({
addon: { guid: addon.guid, id: addon.id, slug: addon.slug },
message: null,
reporter: null,
}),
);
render({ addon });
expect(
screen.getByRole('heading', {
name: 'You reported this add-on',
}),
).toBeInTheDocument();
expect(
screen.queryByRole('button', {
name: 'Report this add-on',
}),
).not.toBeInTheDocument();
});
it('does not disable the "Report this add-on" button if a report is in progress', () => {
// See https://github.com/mozilla/addons-frontend/issues/9086.
store.dispatch(initiateAddonAbuseReportViaFirefox({ addon: fakeAddon }));
render();
expect(
screen.queryByRole('button', {
name: 'Report this add-on',
}),
).not.toHaveClass('Button--disabled');
});
it('does not allow dispatch if there is no content in the textarea', async () => {
const dispatch = jest.spyOn(store, 'dispatch');
render();
await userEvent.click(
screen.getByRole('button', { name: 'Report this add-on' }),
);
await userEvent.click(
screen.getByRole('button', { name: 'Send abuse report' }),
);
expect(
screen.queryByRole('button', { name: 'Sending abuse report' }),
).not.toBeInTheDocument();
expect(dispatch).not.toHaveBeenCalledWith(
expect.objectContaining({ type: SEND_ADDON_ABUSE_REPORT }),
);
});
it('does not allow dispatch if textarea is whitespace', async () => {
const dispatch = jest.spyOn(store, 'dispatch');
render();
await userEvent.click(
screen.getByRole('button', { name: 'Report this add-on' }),
);
await userEvent.type(
screen.getByPlaceholderText(
'Explain how this add-on is violating our policies.',
),
' ',
);
await userEvent.click(
screen.getByRole('button', { name: 'Send abuse report' }),
);
expect(
screen.queryByRole('button', { name: 'Sending abuse report' }),
).not.toBeInTheDocument();
expect(dispatch).not.toHaveBeenCalledWith(
expect.objectContaining({ type: SEND_ADDON_ABUSE_REPORT }),
);
});
it('renders an error if one exists', () => {
const message = 'Some error message';
createFailedErrorHandler({
id: 'ReportAbuseButton',
message,
store,
});
render();
expect(screen.getByText(message)).toBeInTheDocument();
});
});
describe('Tests for ReportAbuseButton with enableFeatureFeedbackFormLinks set', () => {
beforeEach(() => {
const fakeConfig = getMockConfig({
enableFeatureFeedbackFormLinks: true,
});
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
});
it('allows a user to report an add-on for abuse', async () => {
render();
@ -814,7 +533,7 @@ describe(__filename, () => {
).toBeInTheDocument();
});
it('shows a different success message', () => {
it('shows a success message when feedback has been submitted', () => {
const addon = fakeAddon;
const abuseResponse = createFakeAddonAbuseReport({
addon,
@ -829,9 +548,6 @@ describe(__filename, () => {
name: 'You reported this add-on',
}),
).toBeInTheDocument();
expect(
screen.queryByText(/^We can't respond to every abuse report/),
).not.toBeInTheDocument();
});
});

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

@ -1,5 +1,4 @@
/* global window */
import config from 'config';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';
import { oneLine } from 'common-tags';
@ -29,13 +28,10 @@ import {
dispatchSignInActionsWithStore,
fakeAddon,
fakeAuthors,
getMockConfig,
renderPage as defaultRender,
screen,
} from 'tests/unit/helpers';
jest.mock('config');
describe(__filename, () => {
const clientApp = CLIENT_APP_FIREFOX;
const lang = 'en-US';
@ -53,15 +49,9 @@ describe(__filename, () => {
accurate, to the best of my knowledge.`;
let store;
let fakeConfig;
beforeEach(() => {
store = dispatchClientMetadata({ clientApp, lang }).store;
fakeConfig = getMockConfig({ enableFeatureFeedbackForm: true });
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
window.scroll = jest.fn();
});
@ -651,15 +641,6 @@ describe(__filename, () => {
).toBeDisabled();
});
it('renders a Not Found page when enableFeatureFeedbackForm is false', () => {
fakeConfig = { ...fakeConfig, enableFeatureFeedbackForm: false };
render();
expect(
screen.getByText('Oops! We cant find that page'),
).toBeInTheDocument();
});
it('renders errors', () => {
const message = 'Some error message';
createFailedErrorHandler({

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

@ -124,7 +124,6 @@ describe(__filename, () => {
const fakeConfig = getMockConfig({
// This is needed by some of the tests below.
mozillaUserId,
enableFeatureFeedbackFormLinks: false,
});
config.get.mockImplementation((key) => {
return fakeConfig[key];
@ -2395,72 +2394,49 @@ describe(__filename, () => {
});
});
describe('with enableFeatureFeedbackFormLinks=false', () => {
it('does not show an abuse report button', () => {
renderWithCollection();
it('shows an abuse report button', () => {
renderWithCollection();
expect(
screen.queryByRole('link', {
name: 'Report this collection',
}),
).not.toBeInTheDocument();
});
expect(
screen.getByRole('link', {
name: 'Report this collection',
}),
).toBeInTheDocument();
});
describe('with enableFeatureFeedbackFormLinks=true', () => {
beforeEach(() => {
const fakeConfig = getMockConfig({
enableFeatureFeedbackFormLinks: true,
});
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
});
it('does not show an abuse report button when the collection is not loaded yet', () => {
render();
it('shows an abuse report button', () => {
renderWithCollection();
expect(
screen.queryByRole('link', {
name: 'Report this collection',
}),
).not.toBeInTheDocument();
});
expect(
screen.getByRole('link', {
name: 'Report this collection',
}),
).toBeInTheDocument();
});
it('does not show an abuse report button for a owner', () => {
renderWithCollectionForSignedInUser();
it('does not show an abuse report button when the collection is not loaded yet', () => {
render();
expect(
screen.queryByRole('link', {
name: 'Report this collection',
}),
).not.toBeInTheDocument();
});
expect(
screen.queryByRole('link', {
name: 'Report this collection',
}),
).not.toBeInTheDocument();
});
it('renders a confirmation message when the collection has been reported', () => {
const collectionId = 222222;
store.dispatch(loadCollectionAbuseReport({ collectionId }));
it('does not show an abuse report button for a owner', () => {
renderWithCollectionForSignedInUser();
renderWithCollection({ detailProps: { id: collectionId } });
expect(
screen.queryByRole('link', {
name: 'Report this collection',
}),
).not.toBeInTheDocument();
});
it('renders a confirmation message when the collection has been reported', () => {
const collectionId = 222222;
store.dispatch(loadCollectionAbuseReport({ collectionId }));
renderWithCollection({ detailProps: { id: collectionId } });
expect(
screen.queryByRole('link', {
name: 'Report this collection',
}),
).not.toBeInTheDocument();
expect(
screen.getByText('You reported this collection'),
).toBeInTheDocument();
});
expect(
screen.queryByRole('link', {
name: 'Report this collection',
}),
).not.toBeInTheDocument();
expect(
screen.getByText('You reported this collection'),
).toBeInTheDocument();
});
});

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

@ -1,5 +1,4 @@
/* global window */
import config from 'config';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';
@ -26,22 +25,12 @@ import {
createFakeErrorHandler,
dispatchClientMetadata,
dispatchSignInActionsWithStore,
getMockConfig,
renderPage as defaultRender,
screen,
} from 'tests/unit/helpers';
jest.mock('config');
describe(__filename, () => {
let fakeConfig;
beforeEach(() => {
fakeConfig = getMockConfig({ enableFeatureFeedbackForm: true });
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
window.scroll = jest.fn();
});
@ -172,16 +161,6 @@ describe(__filename, () => {
});
});
it('renders a 404 page when enableFeatureFeedbackForm is false', () => {
fakeConfig = { ...fakeConfig, enableFeatureFeedbackForm: false };
render();
expect(
screen.getByText('Oops! We cant find that page'),
).toBeInTheDocument();
});
it('renders a 404 page when the API returned a 404', () => {
const authorId = 1234;
const collectionSlug = 'some-collection-slug';

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

@ -1,5 +1,4 @@
/* global window */
import config from 'config';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';
@ -22,22 +21,12 @@ import {
dispatchClientMetadata,
dispatchSignInActionsWithStore,
fakeReview,
getMockConfig,
renderPage as defaultRender,
screen,
} from 'tests/unit/helpers';
jest.mock('config');
describe(__filename, () => {
let fakeConfig;
beforeEach(() => {
fakeConfig = getMockConfig({ enableFeatureFeedbackForm: true });
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
window.scroll = jest.fn();
});
@ -148,16 +137,6 @@ describe(__filename, () => {
});
});
it('renders a 404 page when enableFeatureFeedbackForm is false', () => {
fakeConfig = { ...fakeConfig, enableFeatureFeedbackForm: false };
render();
expect(
screen.getByText('Oops! We cant find that page'),
).toBeInTheDocument();
});
it('renders a 404 page when the API returned a 404', () => {
const ratingId = 1234;
const { store } = dispatchClientMetadata();

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

@ -1,5 +1,4 @@
/* global window */
import config from 'config';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';
@ -26,22 +25,12 @@ import {
createFakeErrorHandler,
dispatchClientMetadata,
dispatchSignInActionsWithStore,
getMockConfig,
renderPage as defaultRender,
screen,
} from 'tests/unit/helpers';
jest.mock('config');
describe(__filename, () => {
let fakeConfig;
beforeEach(() => {
fakeConfig = getMockConfig({ enableFeatureFeedbackForm: true });
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
window.scroll = jest.fn();
});
@ -152,16 +141,6 @@ describe(__filename, () => {
});
});
it('renders a 404 page when enableFeatureFeedbackForm is false', () => {
fakeConfig = { ...fakeConfig, enableFeatureFeedbackForm: false };
render();
expect(
screen.getByText('Oops! We cant find that page'),
).toBeInTheDocument();
});
it('renders a 404 page when the API returned a 404', () => {
const userId = 1234;
const { store } = dispatchClientMetadata();

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

@ -7,7 +7,6 @@ import {
setUserReviews,
FETCH_USER_REVIEWS,
} from 'amo/actions/reviews';
import createLocalState from 'amo/localState';
import { extractId } from 'amo/pages/UserProfile';
import {
EXTENSIONS_BY_AUTHORS_PAGE_SIZE,
@ -15,12 +14,6 @@ import {
THEMES_BY_AUTHORS_PAGE_SIZE,
fetchAddonsByAuthors,
} from 'amo/reducers/addonsByAuthors';
import {
hideUserAbuseReportUI,
loadUserAbuseReport,
sendUserAbuseReport,
showUserAbuseReportUI,
} from 'amo/reducers/userAbuseReports';
import {
fetchUserAccount,
getCurrentUser,
@ -41,7 +34,6 @@ import { sendServerRedirect } from 'amo/reducers/redirectTo';
import {
changeLocation,
createFailedErrorHandler,
createFakeUserAbuseReport,
createUserAccountResponse,
dispatchClientMetadata,
dispatchSignInActionsWithStore,
@ -49,7 +41,6 @@ import {
fakeReview,
getElement,
getElements,
getMockConfig,
loadAddonsByAuthors,
renderPage as defaultRender,
screen,
@ -73,8 +64,6 @@ jest.mock('amo/localState', () =>
}),
);
jest.mock('config');
describe(__filename, () => {
const lang = 'fr';
const clientApp = CLIENT_APP_FIREFOX;
@ -84,10 +73,6 @@ describe(__filename, () => {
beforeEach(() => {
store = dispatchClientMetadata({ clientApp, lang }).store;
const fakeConfig = getMockConfig({ enableFeatureFeedbackFormLinks: false });
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
});
afterEach(() => {
@ -396,7 +381,7 @@ describe(__filename, () => {
signInUserAndRenderUserProfile();
expect(
screen.queryByRole('button', { name: 'Report this user' }),
screen.queryByRole('link', { name: 'Report this user' }),
).not.toBeInTheDocument();
});
@ -406,7 +391,7 @@ describe(__filename, () => {
renderUserProfile();
expect(
screen.getByRole('button', { name: 'Report this user' }),
screen.getByRole('link', { name: 'Report this user' }),
).toBeInTheDocument();
});
@ -414,16 +399,15 @@ describe(__filename, () => {
renderForOtherThanSignedInUser();
expect(
screen.getByRole('button', { name: 'Report this user' }),
screen.getByRole('link', { name: 'Report this user' }),
).toBeInTheDocument();
});
it('still renders a report abuse component if user is not loaded', () => {
renderUserProfile();
expect(
screen.getByRole('button', { name: 'Report this user' }),
).toBeInTheDocument();
// This is a disabled link so we cannot use `getByRole('link')` here.
expect(screen.getByText('Report this user')).toBeInTheDocument();
});
it('renders two AddonsByAuthorsCard', () => {
@ -1219,272 +1203,12 @@ describe(__filename, () => {
});
describe('Tests for ReportUserAbuse', () => {
const errorHandlerId = 'ReportUserAbuse';
it('renders a button that links to the user feedback form when enableFeatureFeedbackFormLinks is set', () => {
const fakeConfig = getMockConfig({
enableFeatureFeedbackFormLinks: true,
});
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
it('renders a button that links to the user feedback form', () => {
renderForOtherThanSignedInUser();
expect(
screen.getByRole('link', { name: 'Report this user' }),
).toBeInTheDocument();
});
it('renders a disabled button if no user exists', () => {
renderUserProfile();
expect(
screen.getByRole('button', { name: 'Report this user' }),
).toBeDisabled();
});
it('shows the preview content when first rendered', () => {
renderUserProfile();
expect(screen.getByClassName('ReportUserAbuse')).not.toHaveClass(
'ReportUserAbuse--is-expanded',
);
expect(
screen.getByRole('button', { name: 'Report this user' }),
).toBeInTheDocument();
});
it('shows more content when the button is clicked', async () => {
const dispatch = jest.spyOn(store, 'dispatch');
const userId = renderForOtherThanSignedInUser();
await userEvent.click(
screen.getByRole('button', { name: 'Report this user' }),
);
expect(dispatch).toHaveBeenCalledWith(
showUserAbuseReportUI({
userId,
}),
);
expect(screen.getByClassName('ReportUserAbuse')).toHaveClass(
'ReportUserAbuse--is-expanded',
);
// The initial button should no longer be visible.
expect(
screen.queryByRole('button', { name: 'Report this user' }),
).not.toBeInTheDocument();
});
it('renders the form in a pre-submitted state', async () => {
renderForOtherThanSignedInUser();
await userEvent.click(
screen.getByRole('button', { name: 'Report this user' }),
);
expect(
screen.getByRole('heading', { name: 'Report this user' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Send abuse report' }),
).toBeInTheDocument();
});
it('hides more content when the cancel button is clicked', async () => {
const dispatch = jest.spyOn(store, 'dispatch');
const userId = renderForOtherThanSignedInUser();
await userEvent.click(
screen.getByRole('button', { name: 'Report this user' }),
);
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
expect(dispatch).toHaveBeenCalledWith(
hideUserAbuseReportUI({
userId,
}),
);
expect(screen.getByClassName('ReportUserAbuse')).not.toHaveClass(
'ReportUserAbuse--is-expanded',
);
});
it('dispatches the send abuse report action', async () => {
const dispatch = jest.spyOn(store, 'dispatch');
const message = 'This user is funny';
const userId = renderForOtherThanSignedInUser();
await userEvent.click(
screen.getByRole('button', { name: 'Report this user' }),
);
await userEvent.type(
screen.getByPlaceholderText(
'Explain how this user is violating our policies.',
),
message,
);
await userEvent.click(
screen.getByRole('button', { name: 'Send abuse report' }),
);
expect(dispatch).toHaveBeenCalledWith(
sendUserAbuseReport({
errorHandlerId,
message,
userId,
}),
);
expect(
screen.getByRole('button', { name: 'Sending abuse report' }),
).toBeDisabled();
});
it('shows a success message and hides the button if report was sent', () => {
const userId = signInUserWithProps();
// Create a user with another userId.
const anotherUserId = userId + 1;
const user = createUserAccountResponse({ id: anotherUserId });
store.dispatch(loadUserAccount({ user }));
const abuseResponse = createFakeUserAbuseReport({
message: 'Seriously, where is my money?!',
user,
});
store.dispatch(
loadUserAbuseReport({
message: abuseResponse.message,
reporter: abuseResponse.reporter,
userId: user.id,
}),
);
renderUserProfile({ userId: anotherUserId });
expect(
screen.getByRole('heading', {
name: 'You reported this user',
}),
).toBeInTheDocument();
expect(
screen.getByText(/^We can't respond to every abuse report/),
).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: 'Report this user' }),
).not.toBeInTheDocument();
});
it('shows a different success message when enableFeatureFeedbackFormLinks is enabled', () => {
const fakeConfig = getMockConfig({
enableFeatureFeedbackFormLinks: true,
});
config.get.mockImplementation((key) => {
return fakeConfig[key];
});
const userId = signInUserWithProps();
// Create a user with another userId.
const anotherUserId = userId + 1;
const user = createUserAccountResponse({ id: anotherUserId });
store.dispatch(loadUserAccount({ user }));
const abuseResponse = createFakeUserAbuseReport({
message: 'Seriously, where is my money?!',
user,
});
store.dispatch(
loadUserAbuseReport({
message: abuseResponse.message,
reporter: abuseResponse.reporter,
userId: user.id,
}),
);
renderUserProfile({ userId: anotherUserId });
expect(
screen.getByRole('heading', {
name: 'You reported this user',
}),
).toBeInTheDocument();
expect(
screen.queryByText(/^We can't respond to every abuse report/),
).not.toBeInTheDocument();
});
it('renders an error if one exists', () => {
const message = 'Some error message';
createFailedErrorHandler({
id: errorHandlerId,
message,
store,
});
renderForOtherThanSignedInUser();
expect(screen.getByText(message)).toBeInTheDocument();
});
it('allows user to submit again if an error occurred', () => {
createFailedErrorHandler({
id: errorHandlerId,
store,
});
renderForOtherThanSignedInUser();
expect(
screen.getByRole('button', { name: 'Report this user' }),
).not.toBeDisabled();
});
describe('Tests for DismissibleTextForm', () => {
const getLocalStateId = (id) =>
`src/amo/components/ReportUserAbuse/index.js-${id}`;
it('recreates LocalState on update when the ID changes', async () => {
const userId = renderForOtherThanSignedInUser();
const anotherUserId = userId + 1;
expect(createLocalState).toHaveBeenCalledWith(getLocalStateId(userId));
store.dispatch(
loadUserAccount({
user: createUserAccountResponse({ id: anotherUserId }),
}),
);
await changeLocation({
history,
pathname: getLocation({ userId: anotherUserId }),
});
expect(createLocalState).toHaveBeenCalledTimes(2);
expect(createLocalState).toHaveBeenCalledWith(
getLocalStateId(anotherUserId),
);
});
it('does not recreate LocalState on update when ID does not change', async () => {
const userId = renderForOtherThanSignedInUser();
expect(createLocalState).toHaveBeenCalledWith(getLocalStateId(userId));
await changeLocation({
history,
pathname: getLocation({ userId }),
});
expect(createLocalState).toHaveBeenCalledTimes(1);
});
});
});
});

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

@ -1,12 +1,8 @@
import abuseReducer, {
SEND_ADDON_ABUSE_REPORT,
finishAddonAbuseReportViaFirefox,
hideAddonAbuseReportUI,
initiateAddonAbuseReportViaFirefox,
initialState,
loadAddonAbuseReport,
sendAddonAbuseReport,
showAddonAbuseReportUI,
} from 'amo/reducers/abuse';
import {
dispatchClientMetadata,
@ -58,38 +54,6 @@ describe(__filename, () => {
});
});
describe('hideAddonAbuseReportUI', () => {
it('sets the uiVisible state to false', () => {
const state = abuseReducer(
initialState,
hideAddonAbuseReportUI({ addon: fakeAddon }),
);
expect(state).toEqual({
byGUID: {
[fakeAddon.guid]: { uiVisible: false },
},
loading: false,
});
});
});
describe('showAddonAbuseReportUI', () => {
it('sets the uiVisible state to true', () => {
const state = abuseReducer(
initialState,
showAddonAbuseReportUI({ addon: fakeAddon }),
);
expect(state).toEqual({
byGUID: {
[fakeAddon.guid]: { uiVisible: true },
},
loading: false,
});
});
});
describe('sendAddonAbuseReport', () => {
let defaultParams;
@ -169,33 +133,4 @@ describe(__filename, () => {
});
});
});
describe('initiateAddonAbuseReportViaFirefox', () => {
it('does not set the loading state to true', () => {
// See https://github.com/mozilla/addons-frontend/issues/9086.
const state = abuseReducer(
initialState,
initiateAddonAbuseReportViaFirefox({ addon: fakeAddon }),
);
expect(state).toEqual(initialState);
});
});
describe('finishAddonAbuseReportViaFirefox', () => {
it('sets the loading state to false', () => {
// Set loading to true by initiating the abuse report via Firefox.
let state = abuseReducer(
initialState,
initiateAddonAbuseReportViaFirefox({ addon: fakeAddon }),
);
state = abuseReducer(state, finishAddonAbuseReportViaFirefox());
expect(state).toEqual({
...initialState,
loading: false,
});
});
});
});

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

@ -1,11 +1,9 @@
import userAbuseReportsReducer, {
SEND_USER_ABUSE_REPORT,
abortUserAbuseReport,
hideUserAbuseReportUI,
initialState,
loadUserAbuseReport,
sendUserAbuseReport,
showUserAbuseReportUI,
} from 'amo/reducers/userAbuseReports';
import {
createFakeUserAbuseReport,
@ -25,15 +23,7 @@ describe(__filename, () => {
it('resets the state of this abuse report', () => {
const userId = createUserAccountResponse({ id: 501 }).id;
let state = userAbuseReportsReducer(
initialState,
showUserAbuseReportUI({ userId }),
);
state = userAbuseReportsReducer(
state,
showUserAbuseReportUI({ userId }),
);
state = userAbuseReportsReducer(
state,
undefined,
sendUserAbuseReport({
errorHandlerId: 'some-error-handler',
message: 'foo',
@ -50,45 +40,12 @@ describe(__filename, () => {
[userId]: {
hasSubmitted: false,
isSubmitting: false,
uiVisible: false,
},
},
});
});
});
describe('hideUserAbuseReportUI', () => {
it('sets the uiVisible state to false', () => {
const userId = createUserAccountResponse();
const state = userAbuseReportsReducer(
initialState,
hideUserAbuseReportUI({ userId }),
);
expect(state).toMatchObject({
byUserId: {
[userId]: { uiVisible: false },
},
});
});
});
describe('showUserAbuseReportUI', () => {
it('sets the uiVisible state to true', () => {
const userId = createUserAccountResponse().id;
const state = userAbuseReportsReducer(
initialState,
showUserAbuseReportUI({ userId }),
);
expect(state).toMatchObject({
byUserId: {
[userId]: { uiVisible: true },
},
});
});
});
describe('sendUserAbuseReport', () => {
function defaultParams(params = {}) {
return {

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

@ -1,10 +1,7 @@
import SagaTester from 'redux-saga-tester';
import * as addonManager from 'amo/addonManager';
import * as api from 'amo/api/abuse';
import abuseReducer, {
finishAddonAbuseReportViaFirefox,
initiateAddonAbuseReportViaFirefox,
loadAddonAbuseReport,
sendAddonAbuseReport,
} from 'amo/reducers/abuse';
@ -20,13 +17,11 @@ import {
describe(__filename, () => {
let errorHandler;
let mockAddonManager;
let mockApi;
let sagaTester;
beforeEach(() => {
errorHandler = createStubErrorHandler();
mockAddonManager = sinon.mock(addonManager);
mockApi = sinon.mock(api);
const initialState = dispatchSignInActions().state;
sagaTester = new SagaTester({
@ -147,89 +142,4 @@ describe(__filename, () => {
);
});
});
describe('initiateAddonAbuseReportViaFirefox', () => {
function _initiateAddonAbuseReportViaFirefox(params) {
sagaTester.dispatch(
initiateAddonAbuseReportViaFirefox({
addon: fakeAddon,
...params,
}),
);
}
it('calls reportAbuse Firefox API', async () => {
const addon = { ...fakeAddon, guid: 'some-guid' };
mockAddonManager
.expects('reportAbuse')
.withArgs(addon.guid)
.resolves(false);
_initiateAddonAbuseReportViaFirefox({ addon });
await sagaTester.waitFor(finishAddonAbuseReportViaFirefox().type);
mockAddonManager.verify();
});
it('dispatches loadAddonAbuseReport after a successful report', async () => {
const addon = { ...fakeAddon, guid: 'some-guid' };
mockAddonManager.expects('reportAbuse').resolves(true);
_initiateAddonAbuseReportViaFirefox({ addon });
const expectedLoadAction = loadAddonAbuseReport({
addon: { guid: addon.guid, id: addon.id, slug: addon.slug },
message: '',
reporter: null,
reporter_name: null,
reporter_email: null,
reason: null,
location: null,
addon_version: null,
});
const loadAction = await sagaTester.waitFor(expectedLoadAction.type);
expect(loadAction).toEqual(expectedLoadAction);
});
it('dispatches finishAddonAbuseReportViaFirefox after a successful report', async () => {
mockAddonManager.expects('reportAbuse').resolves(true);
_initiateAddonAbuseReportViaFirefox();
const expectedAction = finishAddonAbuseReportViaFirefox();
const finishAction = await sagaTester.waitFor(expectedAction.type);
expect(finishAction).toEqual(expectedAction);
});
it('does not dispatch loadAddonAbuseReport after a cancelled report', async () => {
const addon = { ...fakeAddon, guid: 'some-guid' };
mockAddonManager.expects('reportAbuse').resolves(false);
_initiateAddonAbuseReportViaFirefox({ addon });
await sagaTester.waitFor(finishAddonAbuseReportViaFirefox().type);
const unexpectedAction = loadAddonAbuseReport({
addon: { guid: addon.guid, id: addon.id, slug: addon.slug },
message: 'Abuse report via Firefox',
reporter: null,
});
expect(sagaTester.numCalled(unexpectedAction.type)).toEqual(0);
});
it('dispatches finishAddonAbuseReportViaFirefox on error', async () => {
const error = new Error('An error from reportAbuse');
mockAddonManager.expects('reportAbuse').rejects(error);
_initiateAddonAbuseReportViaFirefox();
const expectedAction = finishAddonAbuseReportViaFirefox();
const finishAction = await sagaTester.waitFor(expectedAction.type);
expect(finishAction).toEqual(expectedAction);
});
});
});

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

@ -45,7 +45,6 @@ describe(__filename, () => {
createInstall: sinon.stub().returns(Promise.resolve(fakeInstallObj)),
getAddonByID: sinon.stub(),
addEventListener: sinon.stub(),
reportAbuse: sinon.stub(),
};
});
@ -61,32 +60,6 @@ describe(__filename, () => {
});
});
describe('hasAbuseReportPanelEnabled', () => {
it('returns false if mozAddonManager is not available', () => {
expect(addonManager.hasAbuseReportPanelEnabled()).toEqual(false);
});
it('returns false if abuseReportPanelEnabled is undefined', () => {
expect(addonManager.hasAbuseReportPanelEnabled({})).toEqual(false);
});
it('returns false if abuseReportPanelEnabled is false', () => {
expect(
addonManager.hasAbuseReportPanelEnabled({
abuseReportPanelEnabled: false,
}),
).toEqual(false);
});
it('returns true if abuseReportPanelEnabled is true', () => {
expect(
addonManager.hasAbuseReportPanelEnabled({
abuseReportPanelEnabled: true,
}),
).toEqual(true);
});
});
describe('getAddon()', () => {
it('should call mozAddonManager.getAddonByID() with id', async () => {
const fakeAddon = { ...fakeClientAddon };
@ -118,58 +91,6 @@ describe(__filename, () => {
});
});
describe('reportAbuse()', () => {
it('calls mozAddonManager.reportAbuse() with an id', async () => {
await addonManager.reportAbuse('test-id', {
_mozAddonManager: {
...fakeMozAddonManager,
abuseReportPanelEnabled: true,
},
});
sinon.assert.calledWith(fakeMozAddonManager.reportAbuse, 'test-id');
});
it('returns the result of mozAddonManager.reportAbuse()', async () => {
const reportAbuseResult = true;
fakeMozAddonManager.reportAbuse.returns(
Promise.resolve(reportAbuseResult),
);
const result = await addonManager.reportAbuse('test-id', {
_mozAddonManager: {
...fakeMozAddonManager,
abuseReportPanelEnabled: true,
},
});
expect(result).toEqual(reportAbuseResult);
});
it('rejects if there is no addon manager', () => {
sinon.stub(addonManager, 'hasAddonManager').returns(false);
return addonManager
.reportAbuse('test-id')
.then(unexpectedSuccess, (err) =>
expect(err.message).toEqual('Cannot report abuse via Firefox'),
);
});
it('rejects if abuseReportPanelEnabled is false', () => {
fakeMozAddonManager.reportAbuse.returns(Promise.resolve(true));
return addonManager
.reportAbuse('test-id', {
_mozAddonManager: {
...fakeMozAddonManager,
abuseReportPanelEnabled: false,
},
})
.then(unexpectedSuccess, (err) =>
expect(err.message).toEqual('Cannot report abuse via Firefox'),
);
});
});
describe('install()', () => {
it('should call mozAddonManager.createInstall() with url', async () => {
await addonManager.install(fakeInstallUrl, fakeCallback, {