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