@@ -25,6 +25,7 @@ vi.mock('@/lib/config', () => ({
2525 config : {
2626 urls : {
2727 lms : vi . fn ( ( ) => 'https://lms.example.com' ) ,
28+ studioUrl : vi . fn ( ( ) => 'https://studio.example.com' ) ,
2829 } ,
2930 } ,
3031} ) ) ;
@@ -383,6 +384,85 @@ describe('CourseDetailsPage', () => {
383384 expect ( screen . queryByText ( 'Configuration' ) ) . not . toBeInTheDocument ( ) ;
384385 } ) ;
385386
387+ describe ( 'Authoring tab (platform admin only)' , ( ) => {
388+ const mockCourse = {
389+ display_name : 'Test Course' ,
390+ course_image_asset_path : '/course-image.jpg' ,
391+ course_price : '$99' ,
392+ language : 'English' ,
393+ start_date : '2024-01-01' ,
394+ } ;
395+
396+ const mockCourseDetail = ( ) =>
397+ vi . mocked ( useCourseDetail ) . mockReturnValue ( {
398+ handleFetchCourseEligibilityInfo : mockHandleFetchCourseEligibilityInfo ,
399+ handleFetchCourseInfo : mockHandleFetchCourseInfo ,
400+ handleFetchCourseSyllabus : mockHandleFetchCourseSyllabus ,
401+ handleOpenLesson : mockHandleOpenLesson ,
402+ course : mockCourse ,
403+ courseOutline : null ,
404+ courseEligibility : { btn_label : 'Enroll' , btn_action : vi . fn ( ) , disabled : false } ,
405+ courseOutlineLoading : false ,
406+ courseEligibilityLoading : false ,
407+ courseButtonActionLoading : false ,
408+ courseInfoLoadingState : 'successful' ,
409+ } as any ) ;
410+
411+ it ( 'renders Authoring tab for platform admin' , ( ) => {
412+ vi . mocked ( useGetDepartmentMemberCheckQuery ) . mockReturnValue ( {
413+ data : { is_platform_admin : true } ,
414+ } as any ) ;
415+ mockCourseDetail ( ) ;
416+
417+ render ( < CourseDetailsPage /> ) ;
418+
419+ expect ( screen . getByText ( 'Authoring' ) ) . toBeInTheDocument ( ) ;
420+ } ) ;
421+
422+ it ( 'hides Authoring tab for non-admin users' , ( ) => {
423+ vi . mocked ( useGetDepartmentMemberCheckQuery ) . mockReturnValue ( {
424+ data : { is_platform_admin : false } ,
425+ } as any ) ;
426+ mockCourseDetail ( ) ;
427+
428+ render ( < CourseDetailsPage /> ) ;
429+
430+ expect ( screen . queryByText ( 'Authoring' ) ) . not . toBeInTheDocument ( ) ;
431+ } ) ;
432+
433+ it ( 'Authoring tab points at studioUrl/course/<courseId> in a new tab' , ( ) => {
434+ vi . mocked ( useGetDepartmentMemberCheckQuery ) . mockReturnValue ( {
435+ data : { is_platform_admin : true } ,
436+ } as any ) ;
437+ mockCourseDetail ( ) ;
438+
439+ render ( < CourseDetailsPage /> ) ;
440+
441+ const link = screen . getByText ( 'Authoring' ) . closest ( 'a' ) ;
442+ // The mocked useParams returns the URL-encoded id; the page decodes it.
443+ expect ( link ?. getAttribute ( 'href' ) ) . toBe (
444+ 'https://studio.example.com/course/course-v1:test+course+2024' ,
445+ ) ;
446+ expect ( link ?. getAttribute ( 'target' ) ) . toBe ( '_blank' ) ;
447+ expect ( link ?. getAttribute ( 'rel' ) ) . toContain ( 'noopener' ) ;
448+ } ) ;
449+
450+ it ( 'Authoring tab is rendered after Configuration tab' , ( ) => {
451+ vi . mocked ( useGetDepartmentMemberCheckQuery ) . mockReturnValue ( {
452+ data : { is_platform_admin : true } ,
453+ } as any ) ;
454+ mockCourseDetail ( ) ;
455+
456+ const { container } = render ( < CourseDetailsPage /> ) ;
457+ const tabRow = container . querySelector ( 'div.flex.space-x-8' ) ;
458+ const labels = Array . from ( tabRow ?. children ?? [ ] ) . map ( ( el ) => el . textContent ?. trim ( ) ) ;
459+ const configIdx = labels . indexOf ( 'Configuration' ) ;
460+ const authoringIdx = labels . indexOf ( 'Authoring' ) ;
461+ expect ( configIdx ) . toBeGreaterThanOrEqual ( 0 ) ;
462+ expect ( authoringIdx ) . toBe ( configIdx + 1 ) ;
463+ } ) ;
464+ } ) ;
465+
386466 it ( 'shows skeleton button when loading eligibility' , ( ) => {
387467 const mockCourse = {
388468 display_name : 'Test Course' ,
0 commit comments