chore: unenroll confirm modal tests

This commit is contained in:
Ben Warzeski
2022-06-10 00:44:44 -04:00
parent aa3fc1321d
commit b9420dabb3
6 changed files with 384 additions and 13 deletions

View File

@@ -0,0 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UnenrollConfirmModal component snapshot: modalStates.confirm 1`] = `
<ModalDialog
hasCloseButton={false}
isOpen={true}
onClose={[MockFunction hooks.nullMethod]}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={
Object {
"textAlign": "start",
}
}
>
<ConfirmPane
handleClose={[MockFunction hooks.close]}
handleConfirm={[MockFunction hooks.confirm]}
/>
</div>
</ModalDialog>
`;
exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason given 1`] = `
<ModalDialog
hasCloseButton={false}
isOpen={true}
onClose={[MockFunction hooks.nullMethod]}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={
Object {
"textAlign": "start",
}
}
>
<FinishedPane
gaveReason={true}
handleClose={[MockFunction hooks.closeAndRefresh]}
/>
</div>
</ModalDialog>
`;
exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason skipped 1`] = `
<ModalDialog
hasCloseButton={false}
isOpen={true}
onClose={[MockFunction hooks.nullMethod]}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={
Object {
"textAlign": "start",
}
}
>
<FinishedPane
gaveReason={true}
handleClose={[MockFunction hooks.closeAndRefresh]}
/>
</div>
</ModalDialog>
`;
exports[`UnenrollConfirmModal component snapshot: modalStates.reason 1`] = `
<ModalDialog
hasCloseButton={false}
isOpen={true}
onClose={[MockFunction hooks.nullMethod]}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={
Object {
"textAlign": "start",
}
}
>
<ReasonPane
reason={
Object {
"isSkipped": false,
"reasonProps": "other",
}
}
/>
</div>
</ModalDialog>
`;

View File

@@ -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,
};
};

View File

@@ -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);
});
});
});
});

View File

@@ -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 (
<ModalDialog
isOpen={show}

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { shallow } from 'enzyme';
import { useDispatch } from 'react-redux';
import { UnenrollConfirmModal } from '.';
import * as hooks from './hooks';
jest.mock('./components/ConfirmPane', () => '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(<UnenrollConfirmModal {...{ closeModal, show }} />);
expect(hooks.modalHooks).toHaveBeenCalledWith({ dispatch, closeModal });
});
test('snapshot: modalStates.confirm', () => {
hooks.modalHooks.mockReturnValueOnce(hookProps);
expect(shallow(<UnenrollConfirmModal {...{ closeModal, show }} />)).toMatchSnapshot();
});
test('snapshot: modalStates.finished, reason given', () => {
hooks.modalHooks.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.finished });
expect(shallow(<UnenrollConfirmModal {...{ closeModal, show }} />)).toMatchSnapshot();
});
test('snapshot: modalStates.finished, reason skipped', () => {
hooks.modalHooks.mockReturnValueOnce({
...hookProps,
modalState: hooks.modalStates.finished,
isSkipped: true,
});
expect(shallow(<UnenrollConfirmModal {...{ closeModal, show }} />)).toMatchSnapshot();
});
test('snapshot: modalStates.reason', () => {
hooks.modalHooks.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.reason });
expect(shallow(<UnenrollConfirmModal {...{ closeModal, show }} />)).toMatchSnapshot();
});
});

View File

@@ -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: {