Skip to content

Commit 805a694

Browse files
marslanabdulraufAdolfo R. Brandesclaude
authored andcommitted
feat: Unenroll survey is configurable through environment variable
Port of master PR #738 to frontend-base. Differences from master: - Config added to src/app.ts instead of .env files and src/config/index.js (frontend-base convention) - Uses useAppConfig() hook instead of importing configuration object directly (idiomatic for code running inside the provider tree) - Default is false (master defaults to true) Co-Authored-By: Adolfo R. Brandes <adolfo@opencraft.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1f109e1 commit 805a694

3 files changed

Lines changed: 90 additions & 10 deletions

File tree

src/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const app: App = {
1717
ECOMMERCE_BASE_URL: '',
1818
ORDER_HISTORY_URL: '',
1919
SUPPORT_URL: '',
20+
SHOW_UNENROLL_SURVEY: false,
2021
}
2122
};
2223

src/containers/UnenrollConfirmModal/hooks/index.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React from 'react';
22

3+
import { useAppConfig } from '@openedx/frontend-base';
34
import { StrictDict } from '@src/utils';
5+
import { useCourseData } from '@src/hooks';
6+
import { useUnenrollFromCourse } from '@src/data/hooks';
47

58
import { useUnenrollReasons } from './reasons';
69
import * as module from '.';
@@ -17,12 +20,23 @@ export const modalStates = StrictDict({
1720

1821
export const useUnenrollData = ({ closeModal, cardId }) => {
1922
const [isConfirmed, setIsConfirmed] = module.state.confirmed(false);
20-
const confirm = () => setIsConfirmed(true);
2123
const reason = useUnenrollReasons({ cardId });
24+
const appConfig = useAppConfig();
25+
const courseData = useCourseData(cardId);
26+
const courseId = courseData?.courseRun?.courseId;
27+
28+
const { mutate: unenrollFromCourse } = useUnenrollFromCourse();
29+
30+
const confirm = () => {
31+
if (!appConfig.SHOW_UNENROLL_SURVEY) {
32+
unenrollFromCourse({ courseId });
33+
}
34+
setIsConfirmed(true);
35+
};
2236

2337
let modalState;
2438
if (isConfirmed) {
25-
modalState = (reason.isSubmitted)
39+
modalState = (reason.isSubmitted || !appConfig.SHOW_UNENROLL_SURVEY)
2640
? modalStates.finished : modalStates.reason;
2741
} else {
2842
modalState = modalStates.confirm;

src/containers/UnenrollConfirmModal/hooks/index.test.js

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { MockUseState } from '@src/testUtils';
2+
import { useAppConfig } from '@openedx/frontend-base';
3+
4+
import { useCourseData } from '@src/hooks';
5+
import { useUnenrollFromCourse } from '@src/data/hooks';
26

37
import * as reasons from './reasons';
48
import * as hooks from '.';
@@ -7,8 +11,23 @@ jest.mock('./reasons', () => ({
711
useUnenrollReasons: jest.fn(),
812
}));
913

14+
jest.mock('@src/data/hooks', () => ({
15+
useUnenrollFromCourse: jest.fn(),
16+
}));
17+
18+
jest.mock('@src/hooks', () => ({
19+
useCourseData: jest.fn(),
20+
}));
21+
22+
jest.mock('@openedx/frontend-base', () => ({
23+
useAppConfig: jest.fn(),
24+
}));
25+
1026
const state = new MockUseState(hooks);
1127
const testValue = 'test-value';
28+
const unenrollFromCourse = jest.fn();
29+
useUnenrollFromCourse.mockReturnValue({ mutate: unenrollFromCourse });
30+
useCourseData.mockReturnValue({ courseRun: { courseId: 'test-course-id' } });
1231
let out;
1332

1433
const mockReason = {
@@ -22,6 +41,7 @@ const useUnenrollReasons = jest.fn(() => mockReason);
2241
describe('UnenrollConfirmModal hooks', () => {
2342
beforeEach(() => {
2443
reasons.useUnenrollReasons.mockImplementation(useUnenrollReasons);
44+
useAppConfig.mockReturnValue({ SHOW_UNENROLL_SURVEY: true });
2545
});
2646
const closeModal = jest.fn();
2747
const cardId = 'test-card-id';
@@ -66,22 +86,67 @@ describe('UnenrollConfirmModal hooks', () => {
6686
expect(mockReason.handleClear).toHaveBeenCalled();
6787
});
6888
});
69-
describe('modalState', () => {
70-
it('returns modalStates.finished if confirmed and submitted', () => {
89+
});
90+
91+
describe('SHOW_UNENROLL_SURVEY configuration tests', () => {
92+
beforeEach(() => {
93+
state.mock();
94+
jest.clearAllMocks();
95+
});
96+
afterEach(() => {
97+
state.restore();
98+
});
99+
100+
describe('when SHOW_UNENROLL_SURVEY is true (default)', () => {
101+
beforeEach(() => {
102+
useAppConfig.mockReturnValue({ SHOW_UNENROLL_SURVEY: true });
103+
useCourseData.mockReturnValue({ courseRun: { courseId: 'test-course-id' } });
104+
useUnenrollFromCourse.mockReturnValue({ mutate: unenrollFromCourse });
105+
reasons.useUnenrollReasons.mockImplementation(useUnenrollReasons);
106+
});
107+
108+
test('confirm does not call unenrollFromCourse immediately', () => {
109+
out = createUseUnenrollData();
110+
out.confirm();
111+
expect(unenrollFromCourse).not.toHaveBeenCalled();
112+
expect(state.setState.confirmed).toHaveBeenCalledWith(true);
113+
});
114+
115+
test('modalState returns reason when confirmed but not submitted', () => {
116+
state.mockVal(state.keys.confirmed, true);
117+
reasons.useUnenrollReasons.mockReturnValueOnce({ ...mockReason, isSubmitted: false });
118+
out = createUseUnenrollData();
119+
expect(out.modalState).toEqual(hooks.modalStates.reason);
120+
});
121+
122+
test('modalState returns finished when confirmed and submitted', () => {
71123
state.mockVal(state.keys.confirmed, true);
72124
reasons.useUnenrollReasons.mockReturnValueOnce({ ...mockReason, isSubmitted: true });
73125
out = createUseUnenrollData();
74126
expect(out.modalState).toEqual(hooks.modalStates.finished);
75127
});
76-
it('returns modalStates.reason if confirmed and not submitted', () => {
77-
state.mockVal(state.keys.confirmed, true);
128+
});
129+
130+
describe('when SHOW_UNENROLL_SURVEY is false', () => {
131+
beforeEach(() => {
132+
useAppConfig.mockReturnValue({ SHOW_UNENROLL_SURVEY: false });
133+
useCourseData.mockReturnValue({ courseRun: { courseId: 'test-course-id' } });
134+
useUnenrollFromCourse.mockReturnValue({ mutate: unenrollFromCourse });
135+
reasons.useUnenrollReasons.mockImplementation(useUnenrollReasons);
136+
});
137+
138+
test('confirm calls unenrollFromCourse immediately', () => {
78139
out = createUseUnenrollData();
79-
expect(out.modalState).toEqual(hooks.modalStates.reason);
140+
out.confirm();
141+
expect(unenrollFromCourse).toHaveBeenCalled();
142+
expect(state.setState.confirmed).toHaveBeenCalledWith(true);
80143
});
81-
it('returns modalStates.confirm if not confirmed', () => {
82-
state.mockVal(state.keys.confirmed, false);
144+
145+
test('modalState returns finished when confirmed regardless of submission status', () => {
146+
state.mockVal(state.keys.confirmed, true);
147+
reasons.useUnenrollReasons.mockReturnValueOnce({ ...mockReason, isSubmitted: false });
83148
out = createUseUnenrollData();
84-
expect(out.modalState).toEqual(hooks.modalStates.confirm);
149+
expect(out.modalState).toEqual(hooks.modalStates.finished);
85150
});
86151
});
87152
});

0 commit comments

Comments
 (0)