@@ -10,6 +10,8 @@ import { executeThunk } from '@src/utils';
1010import genericMessages from '@src/generic/help-sidebar/messages' ;
1111import { DATE_FORMAT } from '@src/constants' ;
1212import { getCourseSettingsApiUrl } from '@src/data/api' ;
13+ import { mockWaffleFlags } from '@src/data/apiHooks.mock' ;
14+ import { useUserPermissionsWithAuthzCourse } from '@src/authz/hooks' ;
1315
1416import { CourseAuthoringProvider } from '@src/CourseAuthoringContext' ;
1517import { courseDetailsMock , courseSettingsMock } from './__mocks__' ;
@@ -22,6 +24,18 @@ import scheduleMessages from './schedule-section/messages';
2224import messages from './messages' ;
2325import 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+
2539let axiosMock ;
2640let store ;
2741const courseId = '123' ;
@@ -169,3 +183,97 @@ describe('<ScheduleAndDetails />', () => {
169183 expect ( getByText ( messages . alertFail . defaultMessage ) ) . toBeInTheDocument ( ) ;
170184 } ) ;
171185} ) ;
186+
187+ describe ( '<ScheduleAndDetails /> permissions' , ( ) => {
188+ beforeEach ( ( ) => {
189+ jest . restoreAllMocks ( ) ;
190+ const mocks = initializeMocks ( ) ;
191+ axiosMock = mocks . axiosMock ;
192+ store = mocks . reduxStore ;
193+ axiosMock . onGet ( getCourseDetailsApiUrl ( courseId ) ) . reply ( 200 , courseDetailsMock ) ;
194+ axiosMock . onGet ( getCourseSettingsApiUrl ( courseId ) ) . reply ( 200 , courseSettingsMock ) ;
195+ axiosMock . onPut ( getCourseDetailsApiUrl ( courseId ) ) . reply ( 200 ) ;
196+ jest . mocked ( useUserPermissionsWithAuthzCourse ) . mockReturnValue ( {
197+ isLoading : false ,
198+ isAuthzEnabled : true ,
199+ permissions : {
200+ canViewScheduleAndDetails : true ,
201+ canEditSchedule : true ,
202+ canEditDetails : true ,
203+ } ,
204+ } ) ;
205+ } ) ;
206+
207+ it ( 'renders normally when authz flag is disabled (no regression)' , async ( ) => {
208+ mockWaffleFlags ( { enableAuthzCourseAuthoring : false } ) ;
209+ const { getAllByText } = renderComponent ( ) ;
210+ await waitFor ( ( ) => {
211+ expect ( getAllByText ( messages . headingTitle . defaultMessage ) . length ) . toBeGreaterThan ( 0 ) ;
212+ } ) ;
213+ } ) ;
214+
215+ it ( 'renders normally when user has all permissions' , async ( ) => {
216+ mockWaffleFlags ( { enableAuthzCourseAuthoring : true } ) ;
217+ const { getAllByText } = renderComponent ( ) ;
218+ await waitFor ( ( ) => {
219+ expect ( getAllByText ( messages . headingTitle . defaultMessage ) . length ) . toBeGreaterThan ( 0 ) ;
220+ } ) ;
221+ } ) ;
222+
223+ it ( 'shows PermissionDeniedAlert when user lacks view permission' , async ( ) => {
224+ mockWaffleFlags ( { enableAuthzCourseAuthoring : true } ) ;
225+ jest . mocked ( useUserPermissionsWithAuthzCourse ) . mockReturnValue ( {
226+ isLoading : false ,
227+ isAuthzEnabled : true ,
228+ permissions : { canViewScheduleAndDetails : false , canEditSchedule : false , canEditDetails : false } ,
229+ } ) ;
230+ const { getByTestId } = renderComponent ( ) ;
231+ await waitFor ( ( ) => {
232+ expect ( getByTestId ( 'permissionDeniedAlert' ) ) . toBeInTheDocument ( ) ;
233+ } ) ;
234+ } ) ;
235+
236+ it ( 'disables schedule date inputs when user lacks edit_schedule permission' , async ( ) => {
237+ mockWaffleFlags ( { enableAuthzCourseAuthoring : true } ) ;
238+ jest . mocked ( useUserPermissionsWithAuthzCourse ) . mockReturnValue ( {
239+ isLoading : false ,
240+ isAuthzEnabled : true ,
241+ permissions : { canViewScheduleAndDetails : true , canEditSchedule : false , canEditDetails : true } ,
242+ } ) ;
243+ const { getAllByPlaceholderText } = renderComponent ( ) ;
244+ await waitFor ( ( ) => {
245+ const dateInputs = getAllByPlaceholderText ( DATE_FORMAT . toLocaleUpperCase ( ) ) ;
246+ dateInputs . forEach ( ( input ) => expect ( input ) . toBeDisabled ( ) ) ;
247+ } ) ;
248+ } ) ;
249+
250+ it ( 'disables pacing and details inputs when user lacks edit_details permission' , async ( ) => {
251+ mockWaffleFlags ( { enableAuthzCourseAuthoring : true } ) ;
252+ jest . mocked ( useUserPermissionsWithAuthzCourse ) . mockReturnValue ( {
253+ isLoading : false ,
254+ isAuthzEnabled : true ,
255+ permissions : { canViewScheduleAndDetails : true , canEditSchedule : true , canEditDetails : false } ,
256+ } ) ;
257+ const { getAllByRole } = renderComponent ( ) ;
258+ await waitFor ( ( ) => {
259+ const radios = getAllByRole ( 'radio' ) ;
260+ radios . forEach ( ( radio ) => expect ( radio ) . toBeDisabled ( ) ) ;
261+ } ) ;
262+ } ) ;
263+
264+ it ( 'save button cannot be triggered when user has no edit permissions' , async ( ) => {
265+ mockWaffleFlags ( { enableAuthzCourseAuthoring : true } ) ;
266+ jest . mocked ( useUserPermissionsWithAuthzCourse ) . mockReturnValue ( {
267+ isLoading : false ,
268+ isAuthzEnabled : true ,
269+ permissions : { canViewScheduleAndDetails : true , canEditSchedule : false , canEditDetails : false } ,
270+ } ) ;
271+ const { getAllByPlaceholderText, queryByText } = renderComponent ( ) ;
272+ // Wait for page to load
273+ const dateInputs = await waitFor ( ( ) => getAllByPlaceholderText ( DATE_FORMAT . toLocaleUpperCase ( ) ) ) ;
274+ // All date inputs must be disabled (no edit_schedule permission)
275+ dateInputs . forEach ( ( input ) => expect ( input ) . toBeDisabled ( ) ) ;
276+ // No changes can be made so the save button never appears
277+ expect ( queryByText ( messages . buttonSaveText . defaultMessage ) ) . not . toBeInTheDocument ( ) ;
278+ } ) ;
279+ } ) ;
0 commit comments