diff --git a/src/containers/UnenrollConfirmModal/__snapshots__/index.test.jsx.snap b/src/containers/UnenrollConfirmModal/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..565896c
--- /dev/null
+++ b/src/containers/UnenrollConfirmModal/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,97 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`UnenrollConfirmModal component snapshot: modalStates.confirm 1`] = `
+
+
+
+
+
+`;
+
+exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason given 1`] = `
+
+
+
+
+
+`;
+
+exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason skipped 1`] = `
+
+
+
+
+
+`;
+
+exports[`UnenrollConfirmModal component snapshot: modalStates.reason 1`] = `
+
+
+
+
+
+`;
diff --git a/src/containers/UnenrollConfirmModal/hooks.js b/src/containers/UnenrollConfirmModal/hooks.js
index dd674a3..8d58e13 100644
--- a/src/containers/UnenrollConfirmModal/hooks.js
+++ b/src/containers/UnenrollConfirmModal/hooks.js
@@ -7,7 +7,7 @@ import * as module from './hooks';
export const state = StrictDict({
confirmed: (val) => React.useState(val),
- customReason: (val) => React.useState(val),
+ customOption: (val) => React.useState(val),
isSkipped: (val) => React.useState(val),
selectedReason: (val) => React.useState(val),
submittedReason: (val) => React.useState(val),
@@ -19,11 +19,15 @@ export const modalStates = StrictDict({
finished: 'finished',
});
+export const valueCallback = (cb, prereqs = []) => (
+ React.useCallback(e => cb(e.target.value), prereqs)
+);
+
export const unenrollReasons = () => {
const [selectedReason, setSelectedReason] = module.state.selectedReason(null);
const [submittedReason, setSubmittedReason] = module.state.submittedReason(null);
const [isSkipped, setIsSkipped] = module.state.isSkipped(false);
- const [customOption, setCustomOption] = module.state.customReason('');
+ const [customOption, setCustomOption] = module.state.customOption('');
return {
clear: React.useCallback(() => {
@@ -32,15 +36,21 @@ export const unenrollReasons = () => {
setCustomOption('');
setIsSkipped(false);
}, []),
+
+ value: submittedReason,
+
customOption: {
value: customOption,
- onChange: React.useCallback((e) => setCustomOption(e.target.value), []),
+ onChange: module.valueCallback(setCustomOption),
},
- isSkipped,
- isSubmitted: submittedReason !== null || isSkipped,
+
selected: selectedReason,
- selectOption: React.useCallback((e) => setSelectedReason(e.target.value), []),
+ selectOption: module.valueCallback(setSelectedReason),
+
+ isSkipped,
skip: React.useCallback(() => setIsSkipped(true), [isSkipped]),
+
+ isSubmitted: submittedReason !== null || isSkipped,
submit: React.useCallback(() => {
if (selectedReason === 'custom') {
setSubmittedReason(customOption);
@@ -48,7 +58,6 @@ export const unenrollReasons = () => {
setSubmittedReason(selectedReason);
}
}, [customOption, selectedReason]),
- value: submittedReason,
};
};
@@ -57,7 +66,7 @@ export const modalHooks = ({ closeModal, dispatch }) => {
const confirm = React.useCallback(() => setIsConfirmed(true), []);
- const reason = unenrollReasons();
+ const reason = module.unenrollReasons();
const close = () => {
closeModal();
setIsConfirmed(false);
@@ -66,7 +75,7 @@ export const modalHooks = ({ closeModal, dispatch }) => {
let modalState;
if (isConfirmed) {
- modalState = reason.isSubmitted ? modalStates.finished : modalState.reason;
+ modalState = reason.isSubmitted ? modalStates.finished : modalStates.reason;
} else {
modalState = modalStates.confirm;
}
@@ -74,14 +83,14 @@ export const modalHooks = ({ closeModal, dispatch }) => {
const closeAndRefresh = React.useCallback(() => {
dispatch(thunkActions.app.refreshList());
close();
- }, []);
+ }, [reason, isConfirmed]);
return {
isConfirmed,
confirm,
reason,
+ close: React.useCallback(close, [reason, isConfirmed]),
closeAndRefresh,
- close,
modalState,
};
};
diff --git a/src/containers/UnenrollConfirmModal/hooks.test.js b/src/containers/UnenrollConfirmModal/hooks.test.js
new file mode 100644
index 0000000..3355dac
--- /dev/null
+++ b/src/containers/UnenrollConfirmModal/hooks.test.js
@@ -0,0 +1,206 @@
+import React from 'react';
+
+import { MockUseState, testCardValues } from 'testUtils';
+import * as hooks from './hooks';
+import { thunkActions } from 'data/redux';
+
+jest.mock('data/redux/thunkActions/app', () => ({
+ refreshList: jest.fn((args) => ({ refreshList: args })),
+}));
+
+const state = new MockUseState(hooks);
+const testValue = 'test-value';
+let out;
+let hook;
+
+describe('UnenrollConfirmModal hooks', () => {
+ describe('state fields', () => {
+ state.testGetter(state.keys.confirmed);
+ state.testGetter(state.keys.customOption);
+ state.testGetter(state.keys.isSkipped);
+ state.testGetter(state.keys.selectedReason);
+ state.testGetter(state.keys.submittedReason);
+ });
+ describe('valueCallback', () => {
+ describe('returned react callback', () => {
+ test('calls passed method with event value and prereqs', () => {
+ const cb = jest.fn();
+ const prereqs = ['test', 'values'];
+ const returned = hooks.valueCallback(cb, prereqs).useCallback;
+ expect(returned.prereqs).toEqual(prereqs);
+ returned.cb({ target: { value: testValue } });
+ expect(cb).toHaveBeenCalledWith(testValue);
+ });
+ test('calls passed method with event value and no prereqs if not passed', () => {
+ const cb = jest.fn();
+ const returned = hooks.valueCallback(cb).useCallback;
+ expect(returned.prereqs).toEqual([]);
+ returned.cb({ target: { value: testValue } });
+ expect(cb).toHaveBeenCalledWith(testValue);
+ });
+ });
+ });
+ describe('unenrollReasons', () => {
+ const mockValueCB = (cb) => ({ callback: cb });
+ beforeEach(() => {
+ hook = hooks.unenrollReasons;
+ state.mock();
+ hooks.valueCallback = jest.fn(mockValueCB);
+ out = hook();
+ });
+ afterEach(() => {
+ state.restore();
+ hooks.valueCallback.mockClear();
+ });
+ describe('clear method', () => {
+ it('resets selected and submitted reasons, custom option and isSkipped', () => {
+ const { cb, prereqs } = out.clear.useCallback;
+ expect(prereqs).toEqual([]);
+ cb();
+ expect(state.setState.selectedReason).toHaveBeenCalledWith(null);
+ expect(state.setState.submittedReason).toHaveBeenCalledWith(null);
+ expect(state.setState.customOption).toHaveBeenCalledWith('');
+ expect(state.setState.isSkipped).toHaveBeenCalledWith(false);
+ });
+ });
+ test('value returns submitted reason', () => {
+ state.mockVal(state.keys.submittedReason, testValue);
+ expect(hook().value).toEqual(testValue);
+ });
+ test('customOption.value returns custom option', () => {
+ state.mockVal(state.keys.customOption, testValue);
+ expect(hook().customOption.value).toEqual(testValue);
+ });
+ test('customOption.onChange returns valueCallback for setCustomOption', () => {
+ expect(out.customOption.onChange).toEqual(mockValueCB(state.setState.customOption));
+ });
+ test('selected returns selectedReason', () => {
+ state.mockVal(state.keys.selectedReason, testValue);
+ expect(hook().selected).toEqual(testValue);
+ });
+ test('selectedOption returns valueCallback for setSelectedReason', () => {
+ expect(out.selectOption).toEqual(mockValueCB(state.setState.selectedReason));
+ });
+ test('isSkipped returns state value', () => {
+ state.mockVal(state.keys.isSkipped, testValue);
+ expect(hook().isSkipped).toEqual(testValue);
+ });
+ test('skip returns callback based on isSkipped that sets isSkipped to true', () => {
+ const { cb, prereqs } = out.skip.useCallback;
+ expect(prereqs).toEqual([state.stateVals.isSkipped]);
+ cb();
+ expect(state.setState.isSkipped).toHaveBeenCalledWith(true);
+ });
+ describe('isSubmitted', () => {
+ it('returns false if submittedReason is null and not isSkipped', () => {
+ expect(out.isSubmitted).toEqual(false);
+ });
+ it('returns true if submittedReason is not null', () => {
+ state.mockVal(state.keys.submittedReason, testValue);
+ expect(hook().isSubmitted).toEqual(true);
+ });
+ it('returns true if isSkipped', () => {
+ state.mockVal(state.keys.isSkipped, true);
+ expect(hook().isSubmitted).toEqual(true);
+ });
+ });
+ describe('submit', () => {
+ it('sets customOption as submittedReason if selectedReason is custom', () => {
+ state.mockVal(state.keys.selectedReason, 'custom');
+ state.mockVal(state.keys.customOption, testValue);
+ hook().submit.useCallback.cb();
+ expect(state.setState.submittedReason).toHaveBeenCalledWith(testValue);
+ });
+ it('sets selectedReason as submittedReason if selectedReason is not custom', () => {
+ state.mockVal(state.keys.selectedReason, testValue);
+ state.mockVal(state.keys.customOption, 'customValue');
+ hook().submit.useCallback.cb();
+ expect(state.setState.submittedReason).toHaveBeenCalledWith(testValue);
+ });
+ it('depends on customOption and selectedReason', () => {
+ const customValue = 'custom-value';
+ state.mockVal(state.keys.selectedReason, testValue);
+ state.mockVal(state.keys.customOption, customValue);
+ const { prereqs } = hook().submit.useCallback;
+ expect(prereqs).toContain(testValue);
+ expect(prereqs).toContain(customValue);
+ });
+ });
+ });
+ describe('modalHooks', () => {
+ const closeModal = jest.fn();
+ const dispatch = jest.fn();
+ let mockReason;
+ beforeEach(() => {
+ hook = hooks.modalHooks;
+ mockReason = {
+ isSubmitted: false,
+ clear: jest.fn(),
+ };
+ state.mock();
+ state.mockVal(state.keys.confirmed, testValue);
+ hooks.unenrollReasons = jest.fn(() => mockReason);
+ out = hook({ closeModal, dispatch });
+ });
+ afterEach(() => {
+ state.restore();
+ hooks.unenrollReasons.mockReset();
+ });
+ test('isConfirmed is forwarded from state', () => {
+ expect(out.isConfirmed).toEqual(testValue);
+ });
+ test('confirm is no-prereqs callback that sets isConfirmed to true', () => {
+ const { cb, prereqs } = out.confirm.useCallback;
+ expect(prereqs).toEqual([]);
+ cb();
+ expect(state.setState.confirmed).toHaveBeenCalledWith(true);
+ });
+ test('reason returns unenrollReasons output', () => {
+ expect(out.reason).toEqual(mockReason);
+ });
+ describe('close', () => {
+ test('callback based on reason and isConfirmed', () => {
+ expect(out.close.useCallback.prereqs).toEqual([mockReason, testValue]);
+ });
+ it('calls closeModal, sets isConfirmed to false, and calls reason.clear', () => {
+ out.close.useCallback.cb();
+ expect(closeModal).toHaveBeenCalled();
+ expect(state.setState.confirmed).toHaveBeenCalledWith(false);
+ expect(mockReason.clear).toHaveBeenCalled();
+ });
+ });
+ describe('closeAndRefresh', () => {
+ test('callback based on reason and isConfirmed', () => {
+ expect(out.closeAndRefresh.useCallback.prereqs).toEqual([mockReason, testValue]);
+ });
+ it('calls closeModal, sets isConfirmed to false, and calls reason.clear', () => {
+ out.closeAndRefresh.useCallback.cb();
+ expect(closeModal).toHaveBeenCalled();
+ expect(state.setState.confirmed).toHaveBeenCalledWith(false);
+ expect(mockReason.clear).toHaveBeenCalled();
+ });
+ it('dispatches refreshList thunkAction', () => {
+ out.closeAndRefresh.useCallback.cb();
+ expect(dispatch).toHaveBeenCalledWith(thunkActions.app.refreshList());
+ });
+ });
+ describe('modalState', () => {
+ it('returns modalStates.finished if confirmed and submitted', () => {
+ state.mockVal(state.keys.confirmed, true);
+ hooks.unenrollReasons = jest.fn(() => ({ ...mockReason, isSubmitted: true }));
+ out = hook({ closeModal, dispatch });
+ expect(out.modalState).toEqual(hooks.modalStates.finished);
+ });
+ it('returns modalStates.reason if confirmed and not submitted', () => {
+ state.mockVal(state.keys.confirmed, true);
+ out = hook({ closeModal, dispatch });
+ expect(out.modalState).toEqual(hooks.modalStates.reason);
+ });
+ it('returns modalStates.confirm if not confirmed', () => {
+ state.mockVal(state.keys.confirmed, false);
+ out = hook({ closeModal, dispatch });
+ expect(out.modalState).toEqual(hooks.modalStates.confirm);
+ });
+ });
+ });
+});
diff --git a/src/containers/UnenrollConfirmModal/index.jsx b/src/containers/UnenrollConfirmModal/index.jsx
index 9c33bee..cb4df20 100644
--- a/src/containers/UnenrollConfirmModal/index.jsx
+++ b/src/containers/UnenrollConfirmModal/index.jsx
@@ -12,7 +12,7 @@ import ConfirmPane from './components/ConfirmPane';
import ReasonPane from './components/ReasonPane';
import FinishedPane from './components/FinishedPane';
-import hooks, { modalStates } from './hooks';
+import { modalHooks, modalStates } from './hooks';
export const UnenrollConfirmModal = ({
closeModal,
@@ -25,7 +25,7 @@ export const UnenrollConfirmModal = ({
closeAndRefresh,
close,
modalState,
- } = hooks({ dispatch, closeModal });
+ } = modalHooks({ dispatch, closeModal });
return (
'ConfirmPane');
+jest.mock('./components/ReasonPane', () => 'ReasonPane');
+jest.mock('./components/FinishedPane', () => 'FinishedPane');
+
+jest.mock('./hooks', () => ({
+ __esModule: true,
+ modalStates: jest.requireActual('./hooks').modalStates,
+ modalHooks: jest.fn(),
+}));
+
+describe('UnenrollConfirmModal component', () => {
+ const dispatch = useDispatch();
+ const hookProps = {
+ confirm: jest.fn().mockName('hooks.confirm'),
+ reason: {
+ isSkipped: false,
+ reasonProps: 'other',
+ },
+ close: jest.fn().mockName('hooks.close'),
+ closeAndRefresh: jest.fn().mockName('hooks.closeAndRefresh'),
+ modalState: hooks.modalStates.confirm,
+ };
+ const closeModal = jest.fn().mockName('props.closeModal');
+ const show = true;
+ test('hooks called with dispatch and closeModal props', () => {
+ hooks.modalHooks.mockReturnValueOnce(hookProps);
+ shallow();
+ expect(hooks.modalHooks).toHaveBeenCalledWith({ dispatch, closeModal });
+ });
+ test('snapshot: modalStates.confirm', () => {
+ hooks.modalHooks.mockReturnValueOnce(hookProps);
+ expect(shallow()).toMatchSnapshot();
+ });
+ test('snapshot: modalStates.finished, reason given', () => {
+ hooks.modalHooks.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.finished });
+ expect(shallow()).toMatchSnapshot();
+ });
+ test('snapshot: modalStates.finished, reason skipped', () => {
+ hooks.modalHooks.mockReturnValueOnce({
+ ...hookProps,
+ modalState: hooks.modalStates.finished,
+ isSkipped: true,
+ });
+ expect(shallow()).toMatchSnapshot();
+ });
+ test('snapshot: modalStates.reason', () => {
+ hooks.modalHooks.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.reason });
+ expect(shallow()).toMatchSnapshot();
+ });
+});
diff --git a/src/setupTest.js b/src/setupTest.js
index 7177b9a..7c18c9b 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -77,6 +77,7 @@ jest.mock('@edx/paragon', () => jest.requireActual('testUtils').mockNestedCompon
Hyperlink: 'Hyperlink',
Icon: 'Icon',
IconButton: 'IconButton',
+ ModalDialog: 'ModalDialog',
MultiSelectDropdownFilter: 'MultiSelectDropdownFilter',
OverlayTrigger: 'OverlayTrigger',
Popover: {