Add new illegal_category and illegal_subcategory fields in the feedback forms (#13070)
This commit is contained in:
Родитель
a174fc2ae4
Коммит
b200cefaec
|
@ -334,7 +334,7 @@
|
|||
"bundlewatch": [
|
||||
{
|
||||
"path": "./dist/static/amo-!(i18n-)*.js",
|
||||
"maxSize": "361 kB"
|
||||
"maxSize": "363 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/static/amo-i18n-*.js",
|
||||
|
|
|
@ -817,6 +817,8 @@ type SendRatingAbuseReportParams = {|
|
|||
reason: string | null,
|
||||
reporterEmail: string | null,
|
||||
reporterName: string | null,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
|};
|
||||
|
||||
export type SendRatingAbuseReportAction = {|
|
||||
|
@ -832,6 +834,8 @@ export const sendRatingAbuseReport = ({
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
}: SendRatingAbuseReportParams): SendRatingAbuseReportAction => {
|
||||
invariant(errorHandlerId, 'errorHandlerId is required');
|
||||
invariant(ratingId, 'ratingId is required');
|
||||
|
@ -846,6 +850,8 @@ export const sendRatingAbuseReport = ({
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -22,6 +22,9 @@ export type AbuseReporter = {|
|
|||
username: string,
|
||||
|} | null;
|
||||
|
||||
// We are using snake case in this type because it makes it easier to share
|
||||
// the same set of parameters between the `mozAddonManager.sendAbuseReport()`
|
||||
// method and the direct API call (`reportAddon()` below).
|
||||
export type ReportAddonParams = {|
|
||||
addonId: string,
|
||||
api: ApiState,
|
||||
|
@ -31,12 +34,11 @@ export type ReportAddonParams = {|
|
|||
reason: string | null,
|
||||
location: string | null,
|
||||
addon_version: string | null,
|
||||
illegal_category: string | null,
|
||||
illegal_subcategory: string | null,
|
||||
auth: boolean,
|
||||
|};
|
||||
|
||||
// We are using snake case in this type because it makes it easier to share
|
||||
// the same set of parameters between the `mozAddonManager.sendAbuseReport()`
|
||||
// method and the direct API call (`reportAddon()` below).
|
||||
export type ReportAddonResponse = {|
|
||||
addon: {|
|
||||
guid: string,
|
||||
|
@ -61,6 +63,8 @@ export function reportAddon({
|
|||
reason,
|
||||
location,
|
||||
addon_version,
|
||||
illegal_category,
|
||||
illegal_subcategory,
|
||||
auth,
|
||||
}: ReportAddonParams): Promise<ReportAddonResponse> {
|
||||
return callApi({
|
||||
|
@ -75,6 +79,8 @@ export function reportAddon({
|
|||
reason,
|
||||
location,
|
||||
addon_version,
|
||||
illegal_category,
|
||||
illegal_subcategory,
|
||||
lang: api.lang,
|
||||
},
|
||||
apiState: api,
|
||||
|
@ -88,6 +94,8 @@ export type ReportUserParams = {|
|
|||
message: string | null,
|
||||
reason: string | null,
|
||||
userId: UserId,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
auth: boolean,
|
||||
|};
|
||||
|
||||
|
@ -112,6 +120,8 @@ export function reportUser({
|
|||
reporterEmail,
|
||||
reporterName,
|
||||
userId,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
auth = true,
|
||||
}: ReportUserParams): Promise<ReportUserResponse> {
|
||||
if (!reason) {
|
||||
|
@ -132,6 +142,8 @@ export function reportUser({
|
|||
reporter_email: reporterEmail,
|
||||
reporter_name: reporterName,
|
||||
lang: api.lang,
|
||||
illegal_category: illegalCategory,
|
||||
illegal_subcategory: illegalSubcategory,
|
||||
},
|
||||
apiState: api,
|
||||
});
|
||||
|
@ -144,6 +156,8 @@ export type ReportRatingParams = {|
|
|||
reason: string | null,
|
||||
reporterName: string | null,
|
||||
reporterEmail: string | null,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
auth: boolean,
|
||||
|};
|
||||
|
||||
|
@ -166,6 +180,8 @@ export function reportRating({
|
|||
reason,
|
||||
reporterName,
|
||||
reporterEmail,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
auth,
|
||||
}: ReportRatingParams): Promise<ReportRatingResponse> {
|
||||
if (!reason) {
|
||||
|
@ -186,6 +202,8 @@ export function reportRating({
|
|||
reporter_name: reporterName,
|
||||
reporter_email: reporterEmail,
|
||||
lang: api.lang,
|
||||
illegal_category: illegalCategory,
|
||||
illegal_subcategory: illegalSubcategory,
|
||||
},
|
||||
apiState: api,
|
||||
});
|
||||
|
@ -198,6 +216,8 @@ export type ReportCollectionParams = {|
|
|||
reason: string | null,
|
||||
reporterName: string | null,
|
||||
reporterEmail: string | null,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
auth: boolean,
|
||||
|};
|
||||
|
||||
|
@ -220,6 +240,8 @@ export function reportCollection({
|
|||
reason,
|
||||
reporterName,
|
||||
reporterEmail,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
auth,
|
||||
}: ReportCollectionParams): Promise<ReportCollectionResponse> {
|
||||
if (!reason) {
|
||||
|
@ -240,6 +262,8 @@ export function reportCollection({
|
|||
reporter_name: reporterName,
|
||||
reporter_email: reporterEmail,
|
||||
lang: api.lang,
|
||||
illegal_category: illegalCategory,
|
||||
illegal_subcategory: illegalSubcategory,
|
||||
},
|
||||
apiState: api,
|
||||
});
|
||||
|
|
|
@ -70,7 +70,16 @@ export class AddonFeedbackFormBase extends React.Component<InternalProps> {
|
|||
values: FeedbackFormValues,
|
||||
) => {
|
||||
const { addon, dispatch, errorHandler, installedAddon } = this.props;
|
||||
const { anonymous, email, name, text, category, location } = values;
|
||||
const {
|
||||
anonymous,
|
||||
email,
|
||||
name,
|
||||
text,
|
||||
category,
|
||||
location,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
} = values;
|
||||
|
||||
invariant(addon, 'An add-on is required for a report.');
|
||||
|
||||
|
@ -89,6 +98,8 @@ export class AddonFeedbackFormBase extends React.Component<InternalProps> {
|
|||
? 'addon'
|
||||
: location,
|
||||
addonVersion: installedAddon?.version || null,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
// Only authenticate the API call when the report isn't submitted
|
||||
// anonymously.
|
||||
auth: anonymous === false,
|
||||
|
|
|
@ -30,6 +30,8 @@ export type FeedbackFormValues = {|
|
|||
text: string,
|
||||
category: string | null,
|
||||
location: string | null,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
|};
|
||||
|
||||
type Props = {|
|
||||
|
@ -169,6 +171,373 @@ const getLocationOptions = (
|
|||
];
|
||||
};
|
||||
|
||||
// These categories don't have any subcategories other than "other".
|
||||
export const ILLEGAL_CATEGORIES_WITHOUT_SUBCATEGORIES = [
|
||||
'animal_welfare',
|
||||
'other',
|
||||
];
|
||||
|
||||
export const getIllegalCategoryOptions = (
|
||||
i18n: I18nType,
|
||||
): Array<{| children: string, value: string |}> => {
|
||||
return [
|
||||
{ children: i18n.gettext('Select type'), value: '' },
|
||||
{ value: 'animal_welfare', children: i18n.gettext('Animal welfare') },
|
||||
{
|
||||
value: 'consumer_information',
|
||||
children: i18n.gettext('Consumer information infringements'),
|
||||
},
|
||||
{
|
||||
value: 'data_protection_and_privacy_violations',
|
||||
children: i18n.gettext('Data protection and privacy violations'),
|
||||
},
|
||||
{
|
||||
value: 'illegal_or_harmful_speech',
|
||||
children: i18n.gettext('Illegal or harmful speech'),
|
||||
},
|
||||
{
|
||||
value: 'intellectual_property_infringements',
|
||||
children: i18n.gettext('Intellectual property infringements'),
|
||||
},
|
||||
{
|
||||
value: 'negative_effects_on_civic_discourse_or_elections',
|
||||
children: i18n.gettext(
|
||||
'Negative effects on civic discourse or elections',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'non_consensual_behaviour',
|
||||
children: i18n.gettext('Non-consensual behavior'),
|
||||
},
|
||||
{
|
||||
value: 'pornography_or_sexualized_content',
|
||||
children: i18n.gettext('Pornography or sexualized content'),
|
||||
},
|
||||
{
|
||||
value: 'protection_of_minors',
|
||||
children: i18n.gettext('Protection of minors'),
|
||||
},
|
||||
{
|
||||
value: 'risk_for_public_security',
|
||||
children: i18n.gettext('Risk for public security'),
|
||||
},
|
||||
{ value: 'scams_and_fraud', children: i18n.gettext('Scams or fraud') },
|
||||
{ value: 'self_harm', children: i18n.gettext('Self-harm') },
|
||||
{
|
||||
value: 'unsafe_and_prohibited_products',
|
||||
children: i18n.gettext('Unsafe, non-compliant, or prohibited products'),
|
||||
},
|
||||
{ value: 'violence', children: i18n.gettext('Violence') },
|
||||
{ value: 'other', children: i18n.gettext('Other') },
|
||||
];
|
||||
};
|
||||
|
||||
export const getIllegalSubcategoryOptions = (
|
||||
i18n: I18nType,
|
||||
category: string | null,
|
||||
): Array<{| children: string, value: string |}> => {
|
||||
const options = [{ children: i18n.gettext('Select violation'), value: '' }];
|
||||
|
||||
switch (category) {
|
||||
case 'consumer_information':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'insufficient_information_on_traders',
|
||||
children: i18n.gettext('Insufficient information on traders'),
|
||||
},
|
||||
{
|
||||
value: 'noncompliance_pricing',
|
||||
children: i18n.gettext('Non-compliance with pricing regulations'),
|
||||
},
|
||||
{
|
||||
value: 'hidden_advertisement',
|
||||
children: i18n.gettext(
|
||||
'Hidden advertisement or commercial communication, including by influencers',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'misleading_info_goods_services',
|
||||
children: i18n.gettext(
|
||||
'Misleading information about the characteristics of the goods and services',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'misleading_info_consumer_rights',
|
||||
children: i18n.gettext(
|
||||
'Misleading information about the consumer’s rights',
|
||||
),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'data_protection_and_privacy_violations':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'biometric_data_breach',
|
||||
children: i18n.gettext('Biometric data breach'),
|
||||
},
|
||||
{
|
||||
value: 'missing_processing_ground',
|
||||
children: i18n.gettext('Missing processing ground for data'),
|
||||
},
|
||||
{
|
||||
value: 'right_to_be_forgotten',
|
||||
children: i18n.gettext('Right to be forgotten'),
|
||||
},
|
||||
{
|
||||
value: 'data_falsification',
|
||||
children: i18n.gettext('Data falsification'),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'illegal_or_harmful_speech':
|
||||
options.push(
|
||||
...[
|
||||
{ value: 'defamation', children: i18n.gettext('Defamation') },
|
||||
{ value: 'discrimination', children: i18n.gettext('Discrimination') },
|
||||
{
|
||||
value: 'hate_speech',
|
||||
children: i18n.gettext(
|
||||
'Illegal incitement to violence and hatred based on protected characteristics (hate speech)',
|
||||
),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'intellectual_property_infringements':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'design_infringement',
|
||||
children: i18n.gettext('Design infringements'),
|
||||
},
|
||||
{
|
||||
value: 'geographic_indications_infringement',
|
||||
children: i18n.gettext('Geographical indications infringements'),
|
||||
},
|
||||
{
|
||||
value: 'patent_infringement',
|
||||
children: i18n.gettext('Patent infringements'),
|
||||
},
|
||||
{
|
||||
value: 'trade_secret_infringement',
|
||||
children: i18n.gettext('Trade secret infringements'),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'negative_effects_on_civic_discourse_or_elections':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'violation_eu_law',
|
||||
children: i18n.gettext(
|
||||
'Violation of EU law relevant to civic discourse or elections',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'violation_national_law',
|
||||
children: i18n.gettext(
|
||||
'Violation of national law relevant to civic discourse or elections',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'misinformation_disinformation_disinformation',
|
||||
children: i18n.gettext(
|
||||
'Misinformation, disinformation, foreign information manipulation and interference',
|
||||
),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'non_consensual_behaviour':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'non_consensual_image_sharing',
|
||||
children: i18n.gettext('Non-consensual image sharing'),
|
||||
},
|
||||
{
|
||||
value: 'non_consensual_items_deepfake',
|
||||
children: i18n.gettext(
|
||||
"Non-consensual items containing deepfake or similar technology using a third party's features",
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'online_bullying_intimidation',
|
||||
children: i18n.gettext('Online bullying/intimidation'),
|
||||
},
|
||||
{ value: 'stalking', children: i18n.gettext('Stalking') },
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'pornography_or_sexualized_content':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'adult_sexual_material',
|
||||
children: i18n.gettext('Adult sexual material'),
|
||||
},
|
||||
{
|
||||
value: 'image_based_sexual_abuse',
|
||||
children: i18n.gettext(
|
||||
'Image-based sexual abuse (excluding content depicting minors)',
|
||||
),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'protection_of_minors':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'age_specific_restrictions_minors',
|
||||
children: i18n.gettext(
|
||||
'age-specific restrictions concerning minors',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'child_sexual_abuse_material',
|
||||
children: i18n.gettext('Child sexual abuse material'),
|
||||
},
|
||||
{
|
||||
value: 'grooming_sexual_enticement_minors',
|
||||
children: i18n.gettext('Grooming/sexual enticement of minors'),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'risk_for_public_security':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'illegal_organizations',
|
||||
children: i18n.gettext('Illegal organizations'),
|
||||
},
|
||||
{
|
||||
value: 'risk_environmental_damage',
|
||||
children: i18n.gettext('Risk for environmental damage'),
|
||||
},
|
||||
{
|
||||
value: 'risk_public_health',
|
||||
children: i18n.gettext('Risk for public health'),
|
||||
},
|
||||
{
|
||||
value: 'terrorist_content',
|
||||
children: i18n.gettext('Terrorist content'),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'scams_and_fraud':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'inauthentic_accounts',
|
||||
children: i18n.gettext('Inauthentic accounts'),
|
||||
},
|
||||
{
|
||||
value: 'inauthentic_listings',
|
||||
children: i18n.gettext('Inauthentic listings'),
|
||||
},
|
||||
{
|
||||
value: 'inauthentic_user_reviews',
|
||||
children: i18n.gettext('Inauthentic user reviews'),
|
||||
},
|
||||
{
|
||||
value: 'impersonation_account_hijacking',
|
||||
children: i18n.gettext('Impersonation or account hijacking'),
|
||||
},
|
||||
{ value: 'phishing', children: i18n.gettext('Phishing') },
|
||||
{
|
||||
value: 'pyramid_schemes',
|
||||
children: i18n.gettext('Pyramid schemes'),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'self_harm':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'content_promoting_eating_disorders',
|
||||
children: i18n.gettext('Content promoting eating disorders'),
|
||||
},
|
||||
{
|
||||
value: 'self_mutilation',
|
||||
children: i18n.gettext('Self-mutilation'),
|
||||
},
|
||||
{ value: 'suicide', children: i18n.gettext('Suicide') },
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'unsafe_and_prohibited_products':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'prohibited_products',
|
||||
children: i18n.gettext('Prohibited or restricted products'),
|
||||
},
|
||||
{
|
||||
value: 'unsafe_products',
|
||||
children: i18n.gettext('Unsafe or non-compliant products'),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
case 'violence':
|
||||
options.push(
|
||||
...[
|
||||
{
|
||||
value: 'coordinated_harm',
|
||||
children: i18n.gettext('Coordinated harm'),
|
||||
},
|
||||
{
|
||||
value: 'gender_based_violence',
|
||||
children: i18n.gettext('Gender-based violence'),
|
||||
},
|
||||
{
|
||||
value: 'human_exploitation',
|
||||
children: i18n.gettext('Human exploitation'),
|
||||
},
|
||||
{
|
||||
value: 'human_trafficking',
|
||||
children: i18n.gettext('Human trafficking'),
|
||||
},
|
||||
{
|
||||
value: 'incitement_violence_hatred',
|
||||
children: i18n.gettext(
|
||||
'General calls or incitement to violence and/or hatred',
|
||||
),
|
||||
},
|
||||
],
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
// Other should be listed for each category..
|
||||
options.push({ value: 'other', children: i18n.gettext('Something else') });
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
export class FeedbackFormBase extends React.Component<InternalProps, State> {
|
||||
static defaultProps: DefaultProps = {
|
||||
_window: typeof window !== 'undefined' ? window : {},
|
||||
|
@ -208,15 +577,58 @@ export class FeedbackFormBase extends React.Component<InternalProps, State> {
|
|||
newValue = checked;
|
||||
}
|
||||
|
||||
this.setState({ [name]: newValue });
|
||||
let newState = { [name]: newValue };
|
||||
// We should reset the illegal category/subcategory when the category
|
||||
// (reason) is no longer "illegal".
|
||||
if (name === 'category' && value !== CATEGORY_ILLEGAL) {
|
||||
newState = {
|
||||
...newState,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
};
|
||||
} else if (name === 'illegalCategory') {
|
||||
// Some illegal categories do not have any sub-categories other than
|
||||
// "Something else" (other) so we set the subcategory to 'other' by
|
||||
// default here, and we'll hide the input field in the UI. Otherwise, we
|
||||
// reset the subcategory to make sure that the user always see "Select
|
||||
// violation" when the illegal category is updated.
|
||||
newState = {
|
||||
...newState,
|
||||
illegalSubcategory: ILLEGAL_CATEGORIES_WITHOUT_SUBCATEGORIES.includes(
|
||||
value,
|
||||
)
|
||||
? 'other'
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
this.setState(newState);
|
||||
};
|
||||
|
||||
onSubmit: HTMLElementEventHandler = (event: ElementEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { anonymous, email, name, text, category, location } = this.state;
|
||||
const {
|
||||
anonymous,
|
||||
email,
|
||||
name,
|
||||
text,
|
||||
category,
|
||||
location,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
} = this.state;
|
||||
|
||||
this.props.onSubmit({ anonymous, email, name, text, category, location });
|
||||
this.props.onSubmit({
|
||||
anonymous,
|
||||
email,
|
||||
name,
|
||||
text,
|
||||
category,
|
||||
location,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
});
|
||||
};
|
||||
|
||||
getFeedbackFormValues(currentUser: UserType | null): FeedbackFormValues {
|
||||
|
@ -227,6 +639,8 @@ export class FeedbackFormBase extends React.Component<InternalProps, State> {
|
|||
text: '',
|
||||
category: null,
|
||||
location: null,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
};
|
||||
|
||||
// `name` is either the `display_name` when set or `Firefox user XYZ`
|
||||
|
@ -396,6 +810,59 @@ export class FeedbackFormBase extends React.Component<InternalProps, State> {
|
|||
</>
|
||||
)}
|
||||
|
||||
{this.state.category === CATEGORY_ILLEGAL && (
|
||||
<>
|
||||
<label
|
||||
className="FeedbackForm-label"
|
||||
htmlFor="feedbackIllegalCategory"
|
||||
>
|
||||
{i18n.gettext('Type of illegal content')}
|
||||
</label>
|
||||
<Select
|
||||
className="FeedbackForm-illegalCategory"
|
||||
id="feedbackIllegalCategory"
|
||||
name="illegalCategory"
|
||||
onChange={this.onFieldChange}
|
||||
value={this.state.illegalCategory}
|
||||
required
|
||||
>
|
||||
{getIllegalCategoryOptions(i18n).map((option) => {
|
||||
return <option key={option.value} {...option} />;
|
||||
})}
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.state.category === CATEGORY_ILLEGAL &&
|
||||
this.state.illegalCategory &&
|
||||
!ILLEGAL_CATEGORIES_WITHOUT_SUBCATEGORIES.includes(
|
||||
this.state.illegalCategory,
|
||||
) && (
|
||||
<>
|
||||
<label
|
||||
className="FeedbackForm-label"
|
||||
htmlFor="feedbackIllegalSubcategory"
|
||||
>
|
||||
{i18n.gettext('Specific violation')}
|
||||
</label>
|
||||
<Select
|
||||
className="FeedbackForm-illegalSubcategory"
|
||||
id="feedbackIllegalSubcategory"
|
||||
name="illegalSubcategory"
|
||||
onChange={this.onFieldChange}
|
||||
value={this.state.illegalSubcategory}
|
||||
required
|
||||
>
|
||||
{getIllegalSubcategoryOptions(
|
||||
i18n,
|
||||
this.state.illegalCategory,
|
||||
).map((option) => {
|
||||
return <option key={option.value} {...option} />;
|
||||
})}
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
|
||||
<label className="FeedbackForm-label" htmlFor="feedbackText">
|
||||
{i18n.gettext('Provide more details')}
|
||||
</label>
|
||||
|
|
|
@ -86,6 +86,8 @@
|
|||
.FeedbackForm-name,
|
||||
.FeedbackForm-email,
|
||||
.FeedbackForm-location,
|
||||
.FeedbackForm-illegalCategory,
|
||||
.FeedbackForm-illegalSubcategory,
|
||||
.FeedbackForm-text,
|
||||
.FeedbackForm .SignedInUser {
|
||||
margin-bottom: 16px;
|
||||
|
|
|
@ -81,7 +81,15 @@ export class CollectionFeedbackBase extends React.Component<InternalProps> {
|
|||
|
||||
onFormSubmitted: (values: FeedbackFormValues) => void = (values) => {
|
||||
const { dispatch, errorHandler, collection } = this.props;
|
||||
const { anonymous, email, name, text, category } = values;
|
||||
const {
|
||||
anonymous,
|
||||
email,
|
||||
name,
|
||||
text,
|
||||
category,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
} = values;
|
||||
|
||||
invariant(collection, 'collection is required');
|
||||
|
||||
|
@ -96,6 +104,8 @@ export class CollectionFeedbackBase extends React.Component<InternalProps> {
|
|||
reason: category,
|
||||
reporterEmail: anonymous ? '' : email,
|
||||
reporterName: anonymous ? '' : name,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -77,7 +77,15 @@ export class RatingFeedbackBase extends React.Component<InternalProps> {
|
|||
|
||||
onFormSubmitted: (values: FeedbackFormValues) => void = (values) => {
|
||||
const { dispatch, errorHandler, review } = this.props;
|
||||
const { anonymous, email, name, text, category } = values;
|
||||
const {
|
||||
anonymous,
|
||||
email,
|
||||
name,
|
||||
text,
|
||||
category,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
} = values;
|
||||
|
||||
invariant(review, 'review is required');
|
||||
|
||||
|
@ -92,6 +100,8 @@ export class RatingFeedbackBase extends React.Component<InternalProps> {
|
|||
reason: category,
|
||||
reporterEmail: anonymous ? '' : email,
|
||||
reporterName: anonymous ? '' : name,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -77,7 +77,15 @@ export class UserFeedbackBase extends React.Component<InternalProps> {
|
|||
|
||||
onFormSubmitted: (values: FeedbackFormValues) => void = (values) => {
|
||||
const { dispatch, errorHandler, user } = this.props;
|
||||
const { anonymous, email, name, text, category } = values;
|
||||
const {
|
||||
anonymous,
|
||||
email,
|
||||
name,
|
||||
text,
|
||||
category,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
} = values;
|
||||
|
||||
invariant(user, 'user is required');
|
||||
|
||||
|
@ -89,6 +97,8 @@ export class UserFeedbackBase extends React.Component<InternalProps> {
|
|||
message: text,
|
||||
reason: category,
|
||||
userId: user.id,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
// Only authenticate the API call when the report isn't submitted
|
||||
// anonymously.
|
||||
auth: anonymous === false,
|
||||
|
|
|
@ -84,12 +84,14 @@ export function loadAddonAbuseReport({
|
|||
type SendAddonAbuseReportParams = {|
|
||||
addonId: string,
|
||||
errorHandlerId: string,
|
||||
reporterEmail?: string | null,
|
||||
reporterName?: string | null,
|
||||
reporterEmail: string | null,
|
||||
reporterName: string | null,
|
||||
message: string | null,
|
||||
reason?: string | null,
|
||||
location?: string | null,
|
||||
addonVersion?: string | null,
|
||||
reason: string | null,
|
||||
location: string | null,
|
||||
addonVersion: string | null,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
auth: boolean,
|
||||
|};
|
||||
|
||||
|
@ -107,6 +109,8 @@ export function sendAddonAbuseReport({
|
|||
reason = null,
|
||||
location = null,
|
||||
addonVersion = null,
|
||||
illegalCategory = null,
|
||||
illegalSubcategory = null,
|
||||
auth,
|
||||
}: SendAddonAbuseReportParams): SendAddonAbuseReportAction {
|
||||
invariant(addonId, 'addonId is required');
|
||||
|
@ -123,6 +127,8 @@ export function sendAddonAbuseReport({
|
|||
reason,
|
||||
location,
|
||||
addonVersion,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
auth,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -27,6 +27,8 @@ type SendCollectionAbuseReportParams = {|
|
|||
reason: string | null,
|
||||
reporterEmail: string | null,
|
||||
reporterName: string | null,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
|};
|
||||
|
||||
export type SendCollectionAbuseReportAction = {|
|
||||
|
@ -42,6 +44,8 @@ export const sendCollectionAbuseReport = ({
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
}: SendCollectionAbuseReportParams): SendCollectionAbuseReportAction => {
|
||||
invariant(errorHandlerId, 'errorHandlerId is required');
|
||||
invariant(collectionId, 'collectionId is required');
|
||||
|
@ -56,6 +60,8 @@ export const sendCollectionAbuseReport = ({
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -71,6 +71,8 @@ export type SendUserAbuseReportParams = {|
|
|||
reporterEmail?: string | null,
|
||||
reporterName?: string | null,
|
||||
userId: UserId,
|
||||
illegalCategory: string | null,
|
||||
illegalSubcategory: string | null,
|
||||
|};
|
||||
|
||||
export type SendUserAbuseReportAction = {|
|
||||
|
@ -86,6 +88,8 @@ export function sendUserAbuseReport({
|
|||
reporterEmail = null,
|
||||
userId,
|
||||
reporterName = null,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
}: SendUserAbuseReportParams): SendUserAbuseReportAction {
|
||||
invariant(errorHandlerId, 'errorHandlerId is required');
|
||||
invariant(userId, 'userId is required');
|
||||
|
@ -100,6 +104,8 @@ export function sendUserAbuseReport({
|
|||
reporterEmail,
|
||||
reporterName,
|
||||
userId,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ export function* reportAddon({
|
|||
reason,
|
||||
location,
|
||||
addonVersion,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
auth,
|
||||
},
|
||||
}: SendAddonAbuseReportAction): Saga {
|
||||
|
@ -43,6 +45,8 @@ export function* reportAddon({
|
|||
reason: reason || null,
|
||||
location: location || null,
|
||||
addon_version: addonVersion || null,
|
||||
illegal_category: illegalCategory || null,
|
||||
illegal_subcategory: illegalSubcategory || null,
|
||||
auth,
|
||||
};
|
||||
let response;
|
||||
|
|
|
@ -22,6 +22,8 @@ export function* reportCollection({
|
|||
reporterEmail,
|
||||
reporterName,
|
||||
collectionId,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
},
|
||||
}: SendCollectionAbuseReportAction): Saga {
|
||||
const errorHandler = createErrorHandler(errorHandlerId);
|
||||
|
@ -39,6 +41,8 @@ export function* reportCollection({
|
|||
reporterName: reporterName || null,
|
||||
reporterEmail: reporterEmail || null,
|
||||
collectionId,
|
||||
illegalCategory: illegalCategory || null,
|
||||
illegalSubcategory: illegalSubcategory || null,
|
||||
};
|
||||
const response = yield call(reportCollectionApi, params);
|
||||
|
||||
|
|
|
@ -435,6 +435,8 @@ function* sendRatingAbuseReport({
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
},
|
||||
}: SendRatingAbuseReportAction): Saga {
|
||||
const errorHandler = createErrorHandler(errorHandlerId);
|
||||
|
@ -452,6 +454,8 @@ function* sendRatingAbuseReport({
|
|||
reason,
|
||||
reporterName: reporterName || null,
|
||||
reporterEmail: reporterEmail || null,
|
||||
illegalCategory: illegalCategory || null,
|
||||
illegalSubcategory: illegalSubcategory || null,
|
||||
};
|
||||
|
||||
const response: ReportRatingResponse = yield call(reportRating, params);
|
||||
|
|
|
@ -22,6 +22,8 @@ export function* reportUser({
|
|||
reporterEmail,
|
||||
reporterName,
|
||||
userId,
|
||||
illegalCategory,
|
||||
illegalSubcategory,
|
||||
},
|
||||
}: SendUserAbuseReportAction): Saga {
|
||||
const errorHandler = createErrorHandler(errorHandlerId);
|
||||
|
@ -39,6 +41,8 @@ export function* reportUser({
|
|||
reporterName: reporterName || null,
|
||||
reporterEmail: reporterEmail || null,
|
||||
userId,
|
||||
illegalCategory: illegalCategory || null,
|
||||
illegalSubcategory: illegalSubcategory || null,
|
||||
};
|
||||
const response = yield call(reportUserApi, params);
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ describe(__filename, () => {
|
|||
location,
|
||||
addon_version,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
|
@ -74,6 +76,8 @@ describe(__filename, () => {
|
|||
reporter_name,
|
||||
location,
|
||||
addon_version,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
auth: true,
|
||||
});
|
||||
|
||||
|
@ -88,7 +92,7 @@ describe(__filename, () => {
|
|||
});
|
||||
}
|
||||
|
||||
it('calls the report add-on abuse API', async () => {
|
||||
it('calls the report user abuse API', async () => {
|
||||
const apiState = dispatchClientMetadata().store.getState().api;
|
||||
const message = 'I do not like this!';
|
||||
const userId = 1234;
|
||||
|
@ -107,18 +111,26 @@ describe(__filename, () => {
|
|||
reporter_email: undefined,
|
||||
reporter_name: undefined,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
.once()
|
||||
.returns(mockResponse({ message, user }));
|
||||
|
||||
await reportUser({ api: apiState, message, userId: user.id });
|
||||
await reportUser({
|
||||
api: apiState,
|
||||
message,
|
||||
userId: user.id,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
});
|
||||
|
||||
mockApi.verify();
|
||||
});
|
||||
|
||||
it('calls the report add-on abuse API with more information', async () => {
|
||||
it('calls the report user abuse API with more information', async () => {
|
||||
const apiState = dispatchClientMetadata().store.getState().api;
|
||||
const reason = 'other';
|
||||
const reporterEmail = 'some-reporter-email';
|
||||
|
@ -139,6 +151,8 @@ describe(__filename, () => {
|
|||
reporter_email: reporterEmail,
|
||||
reporter_name: reporterName,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
|
@ -159,12 +173,14 @@ describe(__filename, () => {
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
});
|
||||
|
||||
mockApi.verify();
|
||||
});
|
||||
|
||||
it('allows the add-on abuse report API to be called anonymously', async () => {
|
||||
it('allows the user abuse report API to be called anonymously', async () => {
|
||||
const apiState = dispatchClientMetadata().store.getState().api;
|
||||
const message = 'I do not like this!';
|
||||
const userId = 1234;
|
||||
|
@ -183,6 +199,8 @@ describe(__filename, () => {
|
|||
reporter_email: undefined,
|
||||
reporter_name: undefined,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
|
@ -194,6 +212,8 @@ describe(__filename, () => {
|
|||
auth: false,
|
||||
message,
|
||||
userId: user.id,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
});
|
||||
|
||||
mockApi.verify();
|
||||
|
@ -224,6 +244,8 @@ describe(__filename, () => {
|
|||
},
|
||||
message: '',
|
||||
reason: 'illegal',
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
...otherProps,
|
||||
},
|
||||
});
|
||||
|
@ -249,6 +271,8 @@ describe(__filename, () => {
|
|||
reporter_email: reporterEmail,
|
||||
reporter_name: reporterName,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
|
@ -262,6 +286,8 @@ describe(__filename, () => {
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
});
|
||||
|
||||
mockApi.verify();
|
||||
|
@ -285,13 +311,22 @@ describe(__filename, () => {
|
|||
reporter_email: undefined,
|
||||
reporter_name: undefined,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
.once()
|
||||
.returns(mockResponse());
|
||||
|
||||
await reportRating({ api: apiState, auth: false, message, ratingId });
|
||||
await reportRating({
|
||||
api: apiState,
|
||||
auth: false,
|
||||
message,
|
||||
ratingId,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
});
|
||||
|
||||
mockApi.verify();
|
||||
});
|
||||
|
@ -320,6 +355,8 @@ describe(__filename, () => {
|
|||
},
|
||||
message: '',
|
||||
reason: 'illegal',
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
...otherProps,
|
||||
},
|
||||
});
|
||||
|
@ -345,6 +382,8 @@ describe(__filename, () => {
|
|||
reporter_email: reporterEmail,
|
||||
reporter_name: reporterName,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
|
@ -358,6 +397,8 @@ describe(__filename, () => {
|
|||
reason,
|
||||
reporterEmail,
|
||||
reporterName,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
});
|
||||
|
||||
mockApi.verify();
|
||||
|
@ -381,6 +422,8 @@ describe(__filename, () => {
|
|||
reporter_email: undefined,
|
||||
reporter_name: undefined,
|
||||
lang: DEFAULT_LANG_IN_TESTS,
|
||||
illegal_category: null,
|
||||
illegal_subcategory: null,
|
||||
},
|
||||
apiState,
|
||||
})
|
||||
|
@ -392,6 +435,8 @@ describe(__filename, () => {
|
|||
auth: false,
|
||||
message,
|
||||
collectionId,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
});
|
||||
|
||||
mockApi.verify();
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
import { fakeI18n } from 'tests/unit/helpers';
|
||||
import {
|
||||
getIllegalCategoryOptions,
|
||||
getIllegalSubcategoryOptions,
|
||||
} from 'amo/components/FeedbackForm';
|
||||
|
||||
describe(__filename, () => {
|
||||
describe('getIllegalCategoryOptions', () => {
|
||||
// See: https://mozilla.github.io/addons-server/topics/api/abuse.html#abuse-report-illegal-category-parameter
|
||||
it('returns a list of illegal categories', () => {
|
||||
const categories = getIllegalCategoryOptions(fakeI18n());
|
||||
|
||||
expect(categories.map((category) => category.value)).toEqual([
|
||||
'',
|
||||
'animal_welfare',
|
||||
'consumer_information',
|
||||
'data_protection_and_privacy_violations',
|
||||
'illegal_or_harmful_speech',
|
||||
'intellectual_property_infringements',
|
||||
'negative_effects_on_civic_discourse_or_elections',
|
||||
'non_consensual_behaviour',
|
||||
'pornography_or_sexualized_content',
|
||||
'protection_of_minors',
|
||||
'risk_for_public_security',
|
||||
'scams_and_fraud',
|
||||
'self_harm',
|
||||
'unsafe_and_prohibited_products',
|
||||
'violence',
|
||||
'other',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIllegalSubcategoryOptions', () => {
|
||||
// See: https://mozilla.github.io/addons-server/topics/api/abuse.html#abuse-report-illegal-subcategory-parameter
|
||||
it.each([
|
||||
['animal_welfare', ['', 'other']],
|
||||
[
|
||||
'consumer_information',
|
||||
[
|
||||
'',
|
||||
'insufficient_information_on_traders',
|
||||
'noncompliance_pricing',
|
||||
'hidden_advertisement',
|
||||
'misleading_info_goods_services',
|
||||
'misleading_info_consumer_rights',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'data_protection_and_privacy_violations',
|
||||
[
|
||||
'',
|
||||
'biometric_data_breach',
|
||||
'missing_processing_ground',
|
||||
'right_to_be_forgotten',
|
||||
'data_falsification',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'illegal_or_harmful_speech',
|
||||
['', 'defamation', 'discrimination', 'hate_speech', 'other'],
|
||||
],
|
||||
[
|
||||
'intellectual_property_infringements',
|
||||
[
|
||||
'',
|
||||
'design_infringement',
|
||||
'geographic_indications_infringement',
|
||||
'patent_infringement',
|
||||
'trade_secret_infringement',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'negative_effects_on_civic_discourse_or_elections',
|
||||
[
|
||||
'',
|
||||
'violation_eu_law',
|
||||
'violation_national_law',
|
||||
'misinformation_disinformation_disinformation',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'non_consensual_behaviour',
|
||||
[
|
||||
'',
|
||||
'non_consensual_image_sharing',
|
||||
'non_consensual_items_deepfake',
|
||||
'online_bullying_intimidation',
|
||||
'stalking',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'pornography_or_sexualized_content',
|
||||
['', 'adult_sexual_material', 'image_based_sexual_abuse', 'other'],
|
||||
],
|
||||
[
|
||||
'protection_of_minors',
|
||||
[
|
||||
'',
|
||||
'age_specific_restrictions_minors',
|
||||
'child_sexual_abuse_material',
|
||||
'grooming_sexual_enticement_minors',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'risk_for_public_security',
|
||||
[
|
||||
'',
|
||||
'illegal_organizations',
|
||||
'risk_environmental_damage',
|
||||
'risk_public_health',
|
||||
'terrorist_content',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'scams_and_fraud',
|
||||
[
|
||||
'',
|
||||
'inauthentic_accounts',
|
||||
'inauthentic_listings',
|
||||
'inauthentic_user_reviews',
|
||||
'impersonation_account_hijacking',
|
||||
'phishing',
|
||||
'pyramid_schemes',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'self_harm',
|
||||
[
|
||||
'',
|
||||
'content_promoting_eating_disorders',
|
||||
'self_mutilation',
|
||||
'suicide',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
[
|
||||
'unsafe_and_prohibited_products',
|
||||
['', 'prohibited_products', 'unsafe_products', 'other'],
|
||||
],
|
||||
[
|
||||
'violence',
|
||||
[
|
||||
'',
|
||||
'coordinated_harm',
|
||||
'gender_based_violence',
|
||||
'human_exploitation',
|
||||
'human_trafficking',
|
||||
'incitement_violence_hatred',
|
||||
'other',
|
||||
],
|
||||
],
|
||||
['other', ['', 'other']],
|
||||
])('returns a list of sub-category for %s', (category, expected) => {
|
||||
const subcategories = getIllegalSubcategoryOptions(fakeI18n(), category);
|
||||
|
||||
expect(subcategories.map((subcategory) => subcategory.value)).toEqual(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -13,6 +13,7 @@ import {
|
|||
CATEGORY_HATEFUL_VIOLENT_DECEPTIVE,
|
||||
CATEGORY_ILLEGAL,
|
||||
CATEGORY_SOMETHING_ELSE,
|
||||
ILLEGAL_CATEGORIES_WITHOUT_SUBCATEGORIES,
|
||||
} from 'amo/components/FeedbackForm';
|
||||
import { extractId } from 'amo/pages/AddonFeedback';
|
||||
import { loadAddonAbuseReport, sendAddonAbuseReport } from 'amo/reducers/abuse';
|
||||
|
@ -314,6 +315,18 @@ describe(__filename, () => {
|
|||
}),
|
||||
defaultLocationLabel,
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
'Consumer information infringements',
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Specific violation',
|
||||
}),
|
||||
'Non-compliance with pricing regulations',
|
||||
);
|
||||
await userEvent.click(
|
||||
screen.getByRole('checkbox', {
|
||||
name: certificationLabel,
|
||||
|
@ -338,10 +351,131 @@ describe(__filename, () => {
|
|||
reason: CATEGORY_ILLEGAL,
|
||||
location: defaultLocation,
|
||||
auth: false,
|
||||
illegalCategory: 'consumer_information',
|
||||
illegalSubcategory: 'noncompliance_pricing',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.each(ILLEGAL_CATEGORIES_WITHOUT_SUBCATEGORIES)(
|
||||
'does not show the "violation" select field when illegal category is %s (the subcategory is set to "other")',
|
||||
async (illegalCategory) => {
|
||||
const dispatch = jest.spyOn(store, 'dispatch');
|
||||
render();
|
||||
|
||||
await userEvent.type(
|
||||
screen.getByRole('textbox', {
|
||||
name: 'Provide more details',
|
||||
}),
|
||||
defaultMessage,
|
||||
);
|
||||
await userEvent.click(
|
||||
screen.getByRole('radio', { name: illegalReasonLabel }),
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Place of the violation',
|
||||
}),
|
||||
defaultLocationLabel,
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
illegalCategory,
|
||||
);
|
||||
await userEvent.click(
|
||||
screen.getByRole('checkbox', {
|
||||
name: certificationLabel,
|
||||
}),
|
||||
);
|
||||
await userEvent.click(
|
||||
screen.getByRole('checkbox', {
|
||||
name: 'File report anonymously',
|
||||
}),
|
||||
);
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', { name: 'Submit report' }),
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByLabelText('Specific violation'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
sendAddonAbuseReport({
|
||||
errorHandlerId: getErrorHandlerId(defaultAddonGUID),
|
||||
addonId: defaultAddonGUID,
|
||||
reporterEmail: '',
|
||||
reporterName: '',
|
||||
message: defaultMessage,
|
||||
reason: CATEGORY_ILLEGAL,
|
||||
location: defaultLocation,
|
||||
auth: false,
|
||||
illegalCategory,
|
||||
illegalSubcategory: 'other',
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('resets the illegal subcategory when the illegal category is updated', async () => {
|
||||
render();
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('radio', { name: illegalReasonLabel }),
|
||||
);
|
||||
// select a category that has no visible subcategory
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
'animal_welfare',
|
||||
);
|
||||
expect(
|
||||
screen.queryByLabelText('Specific violation'),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// select a different category
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
'consumer_information',
|
||||
);
|
||||
// we expect the field for subcategories to be visible now, and its value
|
||||
// should be reset
|
||||
expect(screen.getByLabelText('Specific violation')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('option', { name: 'Select violation' }).selected,
|
||||
).toBe(true);
|
||||
|
||||
// select a subcategory
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Specific violation',
|
||||
}),
|
||||
'hidden_advertisement',
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('option', {
|
||||
name: 'Hidden advertisement or commercial communication, including by influencers',
|
||||
}).selected,
|
||||
).toBe(true);
|
||||
|
||||
// select a new category
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
'data_protection_and_privacy_violations',
|
||||
);
|
||||
// at this point, we should reset the subcategory value/field
|
||||
expect(screen.getByLabelText('Specific violation')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('option', { name: 'Select violation' }).selected,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('dispatches sendAddonAbuseReport action with all fields on submit for a signed-in user', async () => {
|
||||
const name = 'some user name';
|
||||
const email = 'some user email';
|
||||
|
@ -349,6 +483,23 @@ describe(__filename, () => {
|
|||
const dispatch = jest.spyOn(store, 'dispatch');
|
||||
render();
|
||||
|
||||
// start by filing a report for an illegal matter
|
||||
await userEvent.click(
|
||||
screen.getByRole('radio', { name: illegalReasonLabel }),
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
'Consumer information infringements',
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Specific violation',
|
||||
}),
|
||||
'Non-compliance with pricing regulations',
|
||||
);
|
||||
// then change for something else
|
||||
await userEvent.click(
|
||||
screen.getByRole('radio', { name: 'Something else' }),
|
||||
);
|
||||
|
@ -376,6 +527,10 @@ describe(__filename, () => {
|
|||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
location: defaultLocation,
|
||||
auth: true,
|
||||
// We expect these fields to have been reset since the user changed the
|
||||
// reason of the report.
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -394,6 +549,18 @@ describe(__filename, () => {
|
|||
}),
|
||||
defaultLocationLabel,
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
'Consumer information infringements',
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Specific violation',
|
||||
}),
|
||||
'Non-compliance with pricing regulations',
|
||||
);
|
||||
await userEvent.type(
|
||||
screen.getByRole('textbox', { name: 'Provide more details' }),
|
||||
'Some details...',
|
||||
|
@ -422,6 +589,8 @@ describe(__filename, () => {
|
|||
reason: CATEGORY_ILLEGAL,
|
||||
location: defaultLocation,
|
||||
auth: false,
|
||||
illegalCategory: 'consumer_information',
|
||||
illegalSubcategory: 'noncompliance_pricing',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -489,6 +658,8 @@ describe(__filename, () => {
|
|||
reason: 'does_not_work',
|
||||
location: 'addon',
|
||||
auth: false,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -524,6 +695,18 @@ describe(__filename, () => {
|
|||
await userEvent.click(
|
||||
screen.getByRole('radio', { name: illegalReasonLabel }),
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Type of illegal content',
|
||||
}),
|
||||
'Consumer information infringements',
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole('combobox', {
|
||||
name: 'Specific violation',
|
||||
}),
|
||||
'Non-compliance with pricing regulations',
|
||||
);
|
||||
await userEvent.click(
|
||||
screen.getByRole('checkbox', {
|
||||
name: certificationLabel,
|
||||
|
@ -552,6 +735,8 @@ describe(__filename, () => {
|
|||
reason: CATEGORY_ILLEGAL,
|
||||
location: 'addon',
|
||||
auth: false,
|
||||
illegalCategory: 'consumer_information',
|
||||
illegalSubcategory: 'noncompliance_pricing',
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
@ -621,6 +806,8 @@ describe(__filename, () => {
|
|||
location: defaultLocation,
|
||||
addonVersion: version,
|
||||
auth: false,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -313,6 +313,8 @@ describe(__filename, () => {
|
|||
reporterName: signedInName,
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: true,
|
||||
}),
|
||||
);
|
||||
|
@ -355,6 +357,8 @@ describe(__filename, () => {
|
|||
reporterName: '',
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: false,
|
||||
}),
|
||||
);
|
||||
|
@ -495,6 +499,8 @@ describe(__filename, () => {
|
|||
reporterName: '',
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_FEEDBACK_SPAM,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: false,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -352,6 +352,8 @@ describe(__filename, () => {
|
|||
reporterName: '',
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: false,
|
||||
}),
|
||||
);
|
||||
|
@ -387,6 +389,8 @@ describe(__filename, () => {
|
|||
reporterName: signedInName,
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: true,
|
||||
}),
|
||||
);
|
||||
|
@ -427,6 +431,8 @@ describe(__filename, () => {
|
|||
reporterName: '',
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: false,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -332,6 +332,8 @@ describe(__filename, () => {
|
|||
reporterName: '',
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_FEEDBACK_SPAM,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: false,
|
||||
}),
|
||||
);
|
||||
|
@ -367,6 +369,8 @@ describe(__filename, () => {
|
|||
reporterName: signedInName,
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: true,
|
||||
}),
|
||||
);
|
||||
|
@ -407,6 +411,8 @@ describe(__filename, () => {
|
|||
reporterName: '',
|
||||
message: 'Some details...',
|
||||
reason: CATEGORY_SOMETHING_ELSE,
|
||||
illegalCategory: null,
|
||||
illegalSubcategory: null,
|
||||
auth: false,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -67,6 +67,8 @@ describe(__filename, () => {
|
|||
reporterEmail: 'made_up@email',
|
||||
location: 'both',
|
||||
addonVersion: '1.2.3',
|
||||
illegalCategory: 'some illegal category',
|
||||
illegalSubcategory: 'some illegal subcategory',
|
||||
};
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче