Skip to content

Commit 23adfbf

Browse files
committed
fix: solve test issues
1 parent fc383af commit 23adfbf

17 files changed

Lines changed: 351 additions & 6 deletions

File tree

src/schedule-and-details/ScheduleAndDetails.test.jsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { executeThunk } from '@src/utils';
1010
import genericMessages from '@src/generic/help-sidebar/messages';
1111
import { DATE_FORMAT } from '@src/constants';
1212
import { getCourseSettingsApiUrl } from '@src/data/api';
13+
import { mockWaffleFlags } from '@src/data/apiHooks.mock';
14+
import { useUserPermissionsWithAuthzCourse } from '@src/authz/hooks';
1315

1416
import { CourseAuthoringProvider } from '@src/CourseAuthoringContext';
1517
import { courseDetailsMock, courseSettingsMock } from './__mocks__';
@@ -22,6 +24,18 @@ import scheduleMessages from './schedule-section/messages';
2224
import messages from './messages';
2325
import ScheduleAndDetails from '.';
2426

27+
jest.mock('@src/authz/hooks', () => ({
28+
useUserPermissionsWithAuthzCourse: jest.fn().mockReturnValue({
29+
isLoading: false,
30+
isAuthzEnabled: true,
31+
permissions: {
32+
canViewScheduleAndDetails: true,
33+
canEditSchedule: true,
34+
canEditDetails: true,
35+
},
36+
}),
37+
}));
38+
2539
let axiosMock;
2640
let store;
2741
const courseId = '123';
@@ -167,3 +181,97 @@ describe('<ScheduleAndDetails />', () => {
167181
expect(getByText(messages.alertFail.defaultMessage)).toBeInTheDocument();
168182
});
169183
});
184+
185+
describe('<ScheduleAndDetails /> permissions', () => {
186+
beforeEach(() => {
187+
jest.restoreAllMocks();
188+
const mocks = initializeMocks();
189+
axiosMock = mocks.axiosMock;
190+
store = mocks.reduxStore;
191+
axiosMock.onGet(getCourseDetailsApiUrl(courseId)).reply(200, courseDetailsMock);
192+
axiosMock.onGet(getCourseSettingsApiUrl(courseId)).reply(200, courseSettingsMock);
193+
axiosMock.onPut(getCourseDetailsApiUrl(courseId)).reply(200);
194+
jest.mocked(useUserPermissionsWithAuthzCourse).mockReturnValue({
195+
isLoading: false,
196+
isAuthzEnabled: true,
197+
permissions: {
198+
canViewScheduleAndDetails: true,
199+
canEditSchedule: true,
200+
canEditDetails: true,
201+
},
202+
});
203+
});
204+
205+
it('renders normally when authz flag is disabled (no regression)', async () => {
206+
mockWaffleFlags({ enableAuthzCourseAuthoring: false });
207+
const { getAllByText } = renderComponent();
208+
await waitFor(() => {
209+
expect(getAllByText(messages.headingTitle.defaultMessage).length).toBeGreaterThan(0);
210+
});
211+
});
212+
213+
it('renders normally when user has all permissions', async () => {
214+
mockWaffleFlags({ enableAuthzCourseAuthoring: true });
215+
const { getAllByText } = renderComponent();
216+
await waitFor(() => {
217+
expect(getAllByText(messages.headingTitle.defaultMessage).length).toBeGreaterThan(0);
218+
});
219+
});
220+
221+
it('shows PermissionDeniedAlert when user lacks view permission', async () => {
222+
mockWaffleFlags({ enableAuthzCourseAuthoring: true });
223+
jest.mocked(useUserPermissionsWithAuthzCourse).mockReturnValue({
224+
isLoading: false,
225+
isAuthzEnabled: true,
226+
permissions: { canViewScheduleAndDetails: false, canEditSchedule: false, canEditDetails: false },
227+
});
228+
const { getByTestId } = renderComponent();
229+
await waitFor(() => {
230+
expect(getByTestId('permissionDeniedAlert')).toBeInTheDocument();
231+
});
232+
});
233+
234+
it('disables schedule date inputs when user lacks edit_schedule permission', async () => {
235+
mockWaffleFlags({ enableAuthzCourseAuthoring: true });
236+
jest.mocked(useUserPermissionsWithAuthzCourse).mockReturnValue({
237+
isLoading: false,
238+
isAuthzEnabled: true,
239+
permissions: { canViewScheduleAndDetails: true, canEditSchedule: false, canEditDetails: true },
240+
});
241+
const { getAllByPlaceholderText } = renderComponent();
242+
await waitFor(() => {
243+
const dateInputs = getAllByPlaceholderText(DATE_FORMAT.toLocaleUpperCase());
244+
dateInputs.forEach((input) => expect(input).toBeDisabled());
245+
});
246+
});
247+
248+
it('disables pacing and details inputs when user lacks edit_details permission', async () => {
249+
mockWaffleFlags({ enableAuthzCourseAuthoring: true });
250+
jest.mocked(useUserPermissionsWithAuthzCourse).mockReturnValue({
251+
isLoading: false,
252+
isAuthzEnabled: true,
253+
permissions: { canViewScheduleAndDetails: true, canEditSchedule: true, canEditDetails: false },
254+
});
255+
const { getAllByRole } = renderComponent();
256+
await waitFor(() => {
257+
const radios = getAllByRole('radio');
258+
radios.forEach((radio) => expect(radio).toBeDisabled());
259+
});
260+
});
261+
262+
it('save button cannot be triggered when user has no edit permissions', async () => {
263+
mockWaffleFlags({ enableAuthzCourseAuthoring: true });
264+
jest.mocked(useUserPermissionsWithAuthzCourse).mockReturnValue({
265+
isLoading: false,
266+
isAuthzEnabled: true,
267+
permissions: { canViewScheduleAndDetails: true, canEditSchedule: false, canEditDetails: false },
268+
});
269+
const { getAllByPlaceholderText, queryByText } = renderComponent();
270+
// Wait for page to load
271+
const dateInputs = await waitFor(() => getAllByPlaceholderText(DATE_FORMAT.toLocaleUpperCase()));
272+
// All date inputs must be disabled (no edit_schedule permission)
273+
dateInputs.forEach((input) => expect(input).toBeDisabled());
274+
// No changes can be made so the save button never appears
275+
expect(queryByText(messages.buttonSaveText.defaultMessage)).not.toBeInTheDocument();
276+
});
277+
});

src/schedule-and-details/details-section/DetailsSection.test.jsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,26 @@ describe('<DetailsSection />', () => {
5757
getByRole('button', { name: messages.dropdownEmpty.defaultMessage }),
5858
).toBeInTheDocument();
5959
});
60+
61+
it('disables the language dropdown toggle when isEditable is false', () => {
62+
const { getByRole } = render(<RootWrapper {...props} isEditable={false} />);
63+
const toggle = getByRole('button', { name: courseSettingsMock.languageOptions[1][1] });
64+
expect(toggle).toBeDisabled();
65+
});
66+
67+
it('does not call onChange when dropdown item clicked while isEditable is false', () => {
68+
onChangeMock.mockClear();
69+
const { getByRole } = render(<RootWrapper {...props} isEditable={false} />);
70+
// Toggle is disabled, so clicking it does not open the dropdown
71+
const toggle = getByRole('button', { name: courseSettingsMock.languageOptions[1][1] });
72+
expect(toggle).toBeDisabled();
73+
fireEvent.click(toggle);
74+
expect(onChangeMock).not.toHaveBeenCalled();
75+
});
76+
77+
it('enables the language dropdown when isEditable is true', () => {
78+
const { getByRole } = render(<RootWrapper {...props} isEditable />);
79+
const toggle = getByRole('button', { name: courseSettingsMock.languageOptions[1][1] });
80+
expect(toggle).not.toBeDisabled();
81+
});
6082
});

src/schedule-and-details/instructors-section/InstructorsSection.test.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,14 @@ describe('<InstructorsSection />', () => {
106106
}],
107107
}, 'instructorInfo');
108108
});
109+
110+
it('disables add button when isEditable is false', () => {
111+
render(<RootWrapper {...props} isEditable={false} />);
112+
expect(screen.getByRole('button', { name: messages.instructorAdd.defaultMessage })).toBeDisabled();
113+
});
114+
115+
it('enables add button when isEditable is true', () => {
116+
render(<RootWrapper {...props} isEditable />);
117+
expect(screen.getByRole('button', { name: messages.instructorAdd.defaultMessage })).not.toBeDisabled();
118+
});
109119
});

src/schedule-and-details/instructors-section/instructor-container/InstructorContainer.test.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,18 @@ describe('<InstructorContainer />', () => {
122122
fireEvent.click(deleteBtn);
123123
expect(onDeleteMock).toHaveBeenCalledWith(props.idx);
124124
});
125+
126+
it('disables all inputs and delete button when isEditable is false', () => {
127+
const { getAllByRole, getByRole } = render(<RootWrapper {...props} isEditable={false} />);
128+
const textboxes = getAllByRole('textbox');
129+
textboxes.forEach((input) => expect(input).toBeDisabled());
130+
expect(getByRole('button', { name: messages.instructorDelete.defaultMessage })).toBeDisabled();
131+
});
132+
133+
it('enables all inputs and delete button when isEditable is true', () => {
134+
const { getAllByRole, getByRole } = render(<RootWrapper {...props} isEditable />);
135+
const textboxes = getAllByRole('textbox');
136+
textboxes.forEach((input) => expect(input).not.toBeDisabled());
137+
expect(getByRole('button', { name: messages.instructorDelete.defaultMessage })).not.toBeDisabled();
138+
});
125139
});

src/schedule-and-details/introducing-section/IntroducingSection.test.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,14 @@ describe('<IntroducingSection />', () => {
8383
expect(queryAllByText(messages.courseOverviewLabel.defaultMessage).length).toBe(0);
8484
expect(queryAllByText(messages.courseAboutSidebarLabel.defaultMessage).length).toBe(0);
8585
});
86+
87+
it('disables the short description textarea when isEditable is false', () => {
88+
const { getByLabelText } = render(<RootWrapper {...props} isEditable={false} />);
89+
expect(getByLabelText(messages.courseShortDescriptionLabel.defaultMessage)).toBeDisabled();
90+
});
91+
92+
it('enables the short description textarea when isEditable is true', () => {
93+
const { getByLabelText } = render(<RootWrapper {...props} isEditable />);
94+
expect(getByLabelText(messages.courseShortDescriptionLabel.defaultMessage)).not.toBeDisabled();
95+
});
8696
});

src/schedule-and-details/introducing-section/extended-course-details/ExtendedCourseDetails.test.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,16 @@ describe('<ExtendedCourseDetails />', () => {
6464
});
6565
expect(onChangeMock).toHaveBeenCalledWith('abc', 'title');
6666
});
67+
68+
it('disables all inputs when isEditable is false', () => {
69+
const { getAllByRole } = render(<RootWrapper {...props} isEditable={false} />);
70+
const inputs = getAllByRole('textbox');
71+
inputs.forEach((input) => expect(input).toBeDisabled());
72+
});
73+
74+
it('enables all inputs when isEditable is true', () => {
75+
const { getAllByRole } = render(<RootWrapper {...props} isEditable />);
76+
const inputs = getAllByRole('textbox');
77+
inputs.forEach((input) => expect(input).not.toBeDisabled());
78+
});
6779
});

src/schedule-and-details/introducing-section/introduction-video/IntroductionVideo.test.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,18 @@ describe('<IntroductionVideo />', () => {
6262
fireEvent.click(button);
6363
expect(onChangeMock).toHaveBeenCalledWith('', 'introVideo');
6464
});
65+
66+
it('disables input and delete button when isEditable is false', () => {
67+
const initialProps = { ...props, introVideo: 'BvgNgTPTkSo', isEditable: false };
68+
const { getByPlaceholderText, getByRole } = render(<RootWrapper {...initialProps} />);
69+
expect(getByPlaceholderText(messages.courseIntroductionVideoPlaceholder.defaultMessage)).toBeDisabled();
70+
expect(getByRole('button', { name: messages.courseIntroductionVideoDelete.defaultMessage })).toBeDisabled();
71+
});
72+
73+
it('enables input and delete button when isEditable is true', () => {
74+
const initialProps = { ...props, introVideo: 'BvgNgTPTkSo', isEditable: true };
75+
const { getByPlaceholderText, getByRole } = render(<RootWrapper {...initialProps} />);
76+
expect(getByPlaceholderText(messages.courseIntroductionVideoPlaceholder.defaultMessage)).not.toBeDisabled();
77+
expect(getByRole('button', { name: messages.courseIntroductionVideoDelete.defaultMessage })).not.toBeDisabled();
78+
});
6579
});

src/schedule-and-details/learning-outcomes-section/InstructorsSection.test.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,18 @@ describe('<LearningOutcomesSection />', () => {
6666

6767
expect(onChangeMock).toHaveBeenCalledWith(['abc'], 'learningInfo');
6868
});
69+
70+
it('disables input, delete button and add button when isEditable is false', () => {
71+
const { getByPlaceholderText, getByRole } = render(<RootWrapper {...props} isEditable={false} />);
72+
expect(getByPlaceholderText(messages.outcomesInputPlaceholder.defaultMessage)).toBeDisabled();
73+
expect(getByRole('button', { name: messages.outcomesDelete.defaultMessage })).toBeDisabled();
74+
expect(getByRole('button', { name: messages.outcomesAdd.defaultMessage })).toBeDisabled();
75+
});
76+
77+
it('enables input, delete button and add button when isEditable is true', () => {
78+
const { getByPlaceholderText, getByRole } = render(<RootWrapper {...props} isEditable />);
79+
expect(getByPlaceholderText(messages.outcomesInputPlaceholder.defaultMessage)).not.toBeDisabled();
80+
expect(getByRole('button', { name: messages.outcomesDelete.defaultMessage })).not.toBeDisabled();
81+
expect(getByRole('button', { name: messages.outcomesAdd.defaultMessage })).not.toBeDisabled();
82+
});
6983
});

src/schedule-and-details/license-section/LicenseSection.test.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,16 @@ describe('<LicenseSection />', () => {
2525
expect(getByText(messages.licenseTitle.defaultMessage)).toBeInTheDocument();
2626
expect(getByText(messages.licenseDescription.defaultMessage)).toBeInTheDocument();
2727
});
28+
29+
it('disables license type buttons when isEditable is false', () => {
30+
const { getAllByRole } = render(<RootWrapper {...props} isEditable={false} />);
31+
const buttons = getAllByRole('button');
32+
buttons.forEach((button) => expect(button).toBeDisabled());
33+
});
34+
35+
it('enables license type buttons when isEditable is true', () => {
36+
const { getAllByRole } = render(<RootWrapper {...props} isEditable />);
37+
const buttons = getAllByRole('button');
38+
buttons.forEach((button) => expect(button).not.toBeDisabled());
39+
});
2840
});

src/schedule-and-details/license-section/license-commons-options/LicenseCommonsOptions.test.jsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,29 @@ describe('<LicenseCommonsOptions />', () => {
4747
expect(props.onToggleCheckbox).not.toHaveBeenCalled();
4848
fireEvent.click(checkboxList[1]);
4949
expect(props.onToggleCheckbox).toHaveBeenCalledWith(LICENSE_COMMONS_OPTIONS.nonCommercial);
50-
// Note: there is no point in asserting that the checkbox is now checked,
51-
// because it is a controlled component that never changes unless the props change.
52-
// This test should really be implemented in a higher level component/page.
53-
// await waitFor(() => {
54-
// expect(checkboxList[1].checked).toBeFalsy();
55-
// });
50+
});
51+
52+
it('disables all non-fixed checkboxes when isEditable is false', () => {
53+
const { getAllByRole } = render(<RootWrapper {...props} isEditable={false} />);
54+
const checkboxList = getAllByRole('checkbox');
55+
// All checkboxes (including attribution which is always disabled) should be disabled
56+
checkboxList.forEach((checkbox) => expect(checkbox).toBeDisabled());
57+
});
58+
59+
it('does not call onToggleCheckbox when clicked while isEditable is false', () => {
60+
onToggleCheckboxMock.mockClear();
61+
const { getAllByRole } = render(<RootWrapper {...props} isEditable={false} />);
62+
const checkboxList = getAllByRole('checkbox');
63+
fireEvent.click(checkboxList[1]);
64+
expect(onToggleCheckboxMock).not.toHaveBeenCalled();
65+
});
66+
67+
it('non-fixed checkboxes are enabled when isEditable is true', () => {
68+
const { getAllByRole } = render(<RootWrapper {...props} isEditable />);
69+
const checkboxList = getAllByRole('checkbox');
70+
// checkboxList[0] is attribution (always disabled), rest should be enabled
71+
expect(checkboxList[1]).not.toBeDisabled();
72+
expect(checkboxList[2]).not.toBeDisabled();
73+
expect(checkboxList[3]).not.toBeDisabled();
5674
});
5775
});

0 commit comments

Comments
 (0)