@@ -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' ;
@@ -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+ } ) ;
0 commit comments