Remove feedback form feature flags (#12753)
This commit is contained in:
Родитель
9fb30f29a8
Коммит
f96185168e
|
@ -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 can’t 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 can’t 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 can’t 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 can’t 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, {
|
||||
|
|
Загрузка…
Ссылка в новой задаче