@@ -196,7 +202,8 @@ export const mapStateToProps = (state, ownProps) => {
const addon = ownProps.addon;
return {
- abuseReport: addon ? state.abuse.bySlug[addon.slug] : null,
+ abuseReport: addon && state.abuse.bySlug[addon.slug] ?
+ state.abuse.bySlug[addon.slug] : {},
loading: state.abuse.loading,
};
};
diff --git a/src/core/reducers/abuse.js b/src/core/reducers/abuse.js
index 69efbe250e8..e7a8a92f04b 100644
--- a/src/core/reducers/abuse.js
+++ b/src/core/reducers/abuse.js
@@ -1,6 +1,57 @@
/* @flow */
+import type { AddonType } from 'core/types/addons';
+
+export const DISABLE_ADDON_ABUSE_BUTTON_UI = 'DISABLE_ADDON_ABUSE_BUTTON_UI';
+export const ENABLE_ADDON_ABUSE_BUTTON_UI = 'ENABLE_ADDON_ABUSE_BUTTON_UI';
+export const HIDE_ADDON_ABUSE_REPORT_UI = 'HIDE_ADDON_ABUSE_REPORT_UI';
export const LOAD_ADDON_ABUSE_REPORT = 'LOAD_ADDON_ABUSE_REPORT';
export const SEND_ADDON_ABUSE_REPORT = 'SEND_ADDON_ABUSE_REPORT';
+export const SHOW_ADDON_ABUSE_REPORT_UI = 'SHOW_ADDON_ABUSE_REPORT_UI';
+
+type DisableAddonAbuseButtonUIType = { addon: AddonType };
+
+export function disableAbuseButtonUI(
+ { addon }: DisableAddonAbuseButtonUIType = {}
+) {
+ if (!addon) {
+ throw new Error('addon is required');
+ }
+
+ return {
+ type: DISABLE_ADDON_ABUSE_BUTTON_UI,
+ payload: { addon },
+ };
+}
+
+type EnableAddonAbuseButtonUIType = { addon: AddonType };
+
+export function enableAbuseButtonUI(
+ { addon }: EnableAddonAbuseButtonUIType = {}
+) {
+ if (!addon) {
+ throw new Error('addon is required');
+ }
+
+ return {
+ type: ENABLE_ADDON_ABUSE_BUTTON_UI,
+ payload: { addon },
+ };
+}
+
+type HideAddonAbuseReportUIType = { addon: AddonType };
+
+export function hideAddonAbuseReportUI(
+ { addon }: HideAddonAbuseReportUIType = {}
+) {
+ if (!addon) {
+ throw new Error('addon is required');
+ }
+
+ return {
+ type: HIDE_ADDON_ABUSE_REPORT_UI,
+ payload: { addon },
+ };
+}
type LoadAddonAbuseReportType = {
addon: {|
@@ -13,7 +64,7 @@ type LoadAddonAbuseReportType = {
};
export function loadAddonAbuseReport(
- { addon, message, reporter }: LoadAddonAbuseReportType
+ { addon, message, reporter }: LoadAddonAbuseReportType = {}
) {
if (!addon) {
throw new Error('addon is required');
@@ -38,7 +89,7 @@ type SendAddonAbuseReportAction = {|
|};
export function sendAddonAbuseReport(
- { addonSlug, errorHandlerId, message }: SendAddonAbuseReportAction
+ { addonSlug, errorHandlerId, message }: SendAddonAbuseReportAction = {}
) {
if (!addonSlug) {
throw new Error('addonSlug is required');
@@ -56,6 +107,21 @@ export function sendAddonAbuseReport(
};
}
+type ShowAddonAbuseReportUIType = { addon: AddonType };
+
+export function showAddonAbuseReportUI(
+ { addon }: ShowAddonAbuseReportUIType = {}
+) {
+ if (!addon) {
+ throw new Error('addon is required');
+ }
+
+ return {
+ type: SHOW_ADDON_ABUSE_REPORT_UI,
+ payload: { addon },
+ };
+}
+
export const initialState = {
bySlug: {},
loading: false,
@@ -63,7 +129,12 @@ export const initialState = {
type ReducerState = {|
bySlug: {
- [addonSlug: string]: {| message: string, reporter: Object | null |},
+ [addonSlug: string]: {|
+ buttonEnabled?: bool,
+ message: string,
+ reporter: Object | null,
+ uiVisible?: bool,
+ |},
},
loading: bool,
|};
@@ -73,19 +144,63 @@ export default function abuseReducer(
action: Object
) {
switch (action.type) {
- case SEND_ADDON_ABUSE_REPORT:
- return { ...state, loading: true };
+ case DISABLE_ADDON_ABUSE_BUTTON_UI: {
+ const { addon } = action.payload;
+
+ return {
+ ...state,
+ bySlug: {
+ ...state.bySlug,
+ [addon.slug]: { ...state.bySlug[addon.slug], buttonEnabled: false },
+ },
+ };
+ }
+ case ENABLE_ADDON_ABUSE_BUTTON_UI: {
+ const { addon } = action.payload;
+
+ return {
+ ...state,
+ bySlug: {
+ ...state.bySlug,
+ [addon.slug]: { ...state.bySlug[addon.slug], buttonEnabled: true },
+ },
+ };
+ }
+ case HIDE_ADDON_ABUSE_REPORT_UI: {
+ const { addon } = action.payload;
+
+ return {
+ ...state,
+ bySlug: {
+ ...state.bySlug,
+ [addon.slug]: { ...state.bySlug[addon.slug], uiVisible: false },
+ },
+ };
+ }
case LOAD_ADDON_ABUSE_REPORT: {
const { addon, message, reporter } = action.payload;
return {
...state,
bySlug: {
...state.bySlug,
- [addon.slug]: { message, reporter },
+ [addon.slug]: { message, reporter, uiVisible: false },
},
loading: false,
};
}
+ case SEND_ADDON_ABUSE_REPORT:
+ return { ...state, loading: true };
+ case SHOW_ADDON_ABUSE_REPORT_UI: {
+ const { addon } = action.payload;
+
+ return {
+ ...state,
+ bySlug: {
+ ...state.bySlug,
+ [addon.slug]: { ...state.bySlug[addon.slug], uiVisible: true },
+ },
+ };
+ }
default:
return state;
}
diff --git a/tests/unit/amo/components/TestReportAbuseButton.js b/tests/unit/amo/components/TestReportAbuseButton.js
index 656da187e1c..e85873fe647 100644
--- a/tests/unit/amo/components/TestReportAbuseButton.js
+++ b/tests/unit/amo/components/TestReportAbuseButton.js
@@ -1,11 +1,9 @@
import { mount } from 'enzyme';
import React from 'react';
-import ReportAbuseButton, {
- ReportAbuseButtonBase,
- mapStateToProps,
-} from 'amo/components/ReportAbuseButton';
+import ReportAbuseButton from 'amo/components/ReportAbuseButton';
import {
+ enableAbuseButtonUI,
loadAddonAbuseReport,
sendAddonAbuseReport,
} from 'core/reducers/abuse';
@@ -20,38 +18,18 @@ import {
describe(__filename, () => {
function renderMount({
- addon = { ...fakeAddon, slug: 'my-addon' },
- store = dispatchClientMetadata().store,
- ...props
- } = {}) {
- return mount(
- (...args) => callback(...args)}
- i18n={getFakeI18nInst()}
- store={store}
- {...props}
- />
- );
- }
-
- // We use `mount` and the base version of this component for these tests
- // because we need to check the state of the component and call methods
- // directly. The only way to do that is to mount it directly without HOC.
- function mountBaseComponent({
addon = { ...fakeAddon, slug: 'my-addon' },
errorHandler = createStubErrorHandler(),
store = dispatchClientMetadata().store,
...props
} = {}) {
return mount(
- (...args) => callback(...args)}
errorHandler={errorHandler}
i18n={getFakeI18nInst()}
store={store}
- {...mapStateToProps(store.getState(), { addon })}
{...props}
/>
);
@@ -200,9 +178,10 @@ describe(__filename, () => {
it('dispatches when the send button is clicked if textarea has text', () => {
const addon = { ...fakeAddon, slug: 'which-browser' };
- const fakeDispatch = sinon.stub();
const fakeEvent = createFakeEvent();
- const root = mountBaseComponent({ addon, dispatch: fakeDispatch });
+ const { store } = dispatchClientMetadata();
+ const dispatchSpy = sinon.spy(store, 'dispatch');
+ const root = renderMount({ addon, store });
// This simulates entering text into the textarea.
const textarea = root.find('.ReportAbuseButton-textarea textarea');
@@ -210,7 +189,7 @@ describe(__filename, () => {
textarea.simulate('change');
root.find('.ReportAbuseButton-send-report').simulate('click', fakeEvent);
- sinon.assert.calledWith(fakeDispatch, sendAddonAbuseReport({
+ sinon.assert.calledWith(dispatchSpy, sendAddonAbuseReport({
addonSlug: addon.slug,
errorHandlerId: 'create-stub-error-handler-id',
message: 'Opera did it first!',
@@ -224,12 +203,26 @@ describe(__filename, () => {
// be called if the textarea is empty but this function manages to be
// called.
it('does not allow dispatch if there is no content in the textarea', () => {
- const fakeDispatch = sinon.stub();
+ const addon = { ...fakeAddon, slug: 'this-should-not-happen' };
const fakeEvent = createFakeEvent();
- const root = mountBaseComponent({ dispatch: fakeDispatch });
+ const { store } = dispatchClientMetadata();
+ const dispatchSpy = sinon.spy(store, 'dispatch');
+ const root = renderMount({ addon, store });
+
+ // We enable the button with an empty textarea; this never happens
+ // normally but we can force it here for testing.
+ store.dispatch(enableAbuseButtonUI({ addon }));
+ dispatchSpy.reset();
+ fakeEvent.preventDefault.reset();
+
+ // Make sure the button isn't disabled.
+ expect(root.find('.ReportAbuseButton-send-report').prop('disabled'))
+ .toEqual(false);
+ root.find('.ReportAbuseButton-send-report').simulate('click', fakeEvent);
- root.instance().sendReport(fakeEvent);
- sinon.assert.notCalled(fakeDispatch);
+ sinon.assert.notCalled(dispatchSpy);
+ // Make sure preventDefault was called; we then know the sendReport()
+ // method was called.
sinon.assert.called(fakeEvent.preventDefault);
});
});
diff --git a/tests/unit/core/reducers/test_abuse.js b/tests/unit/core/reducers/test_abuse.js
index 8b5c9ec1942..398a3ac2855 100644
--- a/tests/unit/core/reducers/test_abuse.js
+++ b/tests/unit/core/reducers/test_abuse.js
@@ -1,8 +1,12 @@
import abuseReducer, {
SEND_ADDON_ABUSE_REPORT,
+ disableAbuseButtonUI,
+ enableAbuseButtonUI,
+ hideAddonAbuseReportUI,
initialState,
loadAddonAbuseReport,
sendAddonAbuseReport,
+ showAddonAbuseReportUI,
} from 'core/reducers/abuse';
import { dispatchClientMetadata, fakeAddon } from 'tests/unit/amo/helpers';
import { createFakeAddonAbuseReport } from 'tests/unit/helpers';
@@ -41,6 +45,86 @@ describe(__filename, () => {
});
});
+ describe('disableAbuseButtonUI', () => {
+ it('sets the buttonEnabled state to false', () => {
+ const state = abuseReducer(
+ initialState, disableAbuseButtonUI({ addon: fakeAddon }));
+
+ expect(state).toEqual({
+ bySlug: {
+ [fakeAddon.slug]: { buttonEnabled: false },
+ },
+ loading: false,
+ });
+ });
+
+ it('requires an addon param', () => {
+ expect(() => {
+ disableAbuseButtonUI();
+ }).toThrow('addon is required');
+ });
+ });
+
+ describe('enableAbuseButtonUI', () => {
+ it('sets the buttonEnabled state to true', () => {
+ const state = abuseReducer(
+ initialState, enableAbuseButtonUI({ addon: fakeAddon }));
+
+ expect(state).toEqual({
+ bySlug: {
+ [fakeAddon.slug]: { buttonEnabled: true },
+ },
+ loading: false,
+ });
+ });
+
+ it('requires an addon param', () => {
+ expect(() => {
+ enableAbuseButtonUI();
+ }).toThrow('addon is required');
+ });
+ });
+
+ describe('hideAddonAbuseReportUI', () => {
+ it('sets the uiVisible state to false', () => {
+ const state = abuseReducer(
+ initialState, hideAddonAbuseReportUI({ addon: fakeAddon }));
+
+ expect(state).toEqual({
+ bySlug: {
+ [fakeAddon.slug]: { uiVisible: false },
+ },
+ loading: false,
+ });
+ });
+
+ it('requires an addon param', () => {
+ expect(() => {
+ hideAddonAbuseReportUI();
+ }).toThrow('addon is required');
+ });
+ });
+
+ describe('showAddonAbuseReportUI', () => {
+ it('sets the uiVisible state to true', () => {
+ const state = abuseReducer(
+ initialState, showAddonAbuseReportUI({ addon: fakeAddon }));
+
+ expect(state).toEqual({
+ bySlug: {
+ [fakeAddon.slug]: { uiVisible: true },
+ },
+ loading: false,
+ });
+ });
+
+ it('requires an addon param', () => {
+ expect(() => {
+ showAddonAbuseReportUI();
+ }).toThrow('addon is required');
+ });
+ });
+
describe('sendAddonAbuseReport', () => {
let defaultParams;