1- import { getConfig , setConfig } from '@edx/frontend-platform' ;
1+ import { getConfig } from '@edx/frontend-platform' ;
22import { cloneDeep } from 'lodash' ;
33import { closestCorners } from '@dnd-kit/core' ;
44import { logError } from '@edx/frontend-platform/logging' ;
@@ -9,8 +9,7 @@ import { executeThunk } from '@src/utils';
99import configureModalMessages from '@src/generic/configure-modal/messages' ;
1010import pasteButtonMessages from '@src/generic/clipboard/paste-component/messages' ;
1111import { getApiBaseUrl , getClipboardUrl } from '@src/generic/data/api' ;
12- import { postXBlockBaseApiUrl } from '@src/course-unit/data/api' ;
13- import { COMPONENT_TYPES } from '@src/generic/block-type-utils/constants' ;
12+ import { ContainerType } from '@src/generic/key-utils' ;
1413import { getDownstreamApiUrl } from '@src/generic/unlink-modal/data/api' ;
1514import { CourseAuthoringProvider } from '@src/CourseAuthoringContext' ;
1615import {
@@ -53,14 +52,13 @@ import {
5352 courseSectionMock ,
5453 courseSubsectionMock ,
5554} from './__mocks__' ;
56- import { COURSE_BLOCK_NAMES , VIDEO_SHARING_OPTIONS } from './constants' ;
55+ import { COURSE_BLOCK_NAMES } from './constants' ;
5756import CourseOutline from './CourseOutline' ;
5857
5958import messages from './messages' ;
6059import headerMessages from './header-navigations/messages' ;
6160import cardHeaderMessages from './card-header/messages' ;
6261import enableHighlightsModalMessages from './enable-highlights-modal/messages' ;
63- import statusBarMessages from './status-bar/messages' ;
6462import subsectionMessages from './subsection-card/messages' ;
6563import pageAlertMessages from './page-alerts/messages' ;
6664import {
@@ -74,9 +72,8 @@ let axiosMock: import('axios-mock-adapter/types');
7472let store ;
7573const mockPathname = '/foo-bar' ;
7674const courseId = '123' ;
77- const getContainerKey = jest . fn ( ) . mockReturnValue ( 'lct:org:lib:unit:1' ) ;
78- const getContainerType = jest . fn ( ) . mockReturnValue ( 'unit' ) ;
7975const clearSelection = jest . fn ( ) ;
76+ const startCurrentFlow = jest . fn ( ) ;
8077let selectedContainerId : string | undefined ;
8178let courseOutlineIndexMock = cloneDeep ( originalCourseOutlineIndexMock ) ;
8279
@@ -85,6 +82,7 @@ jest.mock('@src/course-outline/outline-sidebar/OutlineSidebarContext', () => ({
8582 ...jest . requireActual ( '@src/course-outline/outline-sidebar/OutlineSidebarContext' ) ,
8683 useOutlineSidebarContext : ( ) => ( {
8784 ...jest . requireActual ( '@src/course-outline/outline-sidebar/OutlineSidebarContext' ) . useOutlineSidebarContext ( ) ,
85+ startCurrentFlow,
8886 clearSelection,
8987 selectedContainerState : ( ( ) => ( selectedContainerId ? { currentId : selectedContainerId } : undefined ) ) ( ) ,
9088 } ) ,
@@ -109,24 +107,6 @@ jest.mock('./data/api', () => ({
109107 getTagsCount : ( ) => jest . fn ( ) . mockResolvedValue ( { } ) ,
110108} ) ) ;
111109
112- // Mock LibraryAndComponentPicker to call onComponentSelected on click
113- jest . mock ( '@src/library-authoring/component-picker' , ( ) => ( {
114- LibraryAndComponentPicker : ( props ) => {
115- const onClick = ( ) => {
116- // eslint-disable-next-line react/prop-types
117- props . onComponentSelected ( {
118- usageKey : getContainerKey ( ) ,
119- blockType : getContainerType ( ) ,
120- } ) ;
121- } ;
122- return (
123- < button type = "submit" onClick = { onClick } >
124- Dummy button
125- </ button >
126- ) ;
127- } ,
128- } ) ) ;
129-
130110jest . mock ( '@edx/frontend-platform/logging' , ( ) => ( {
131111 logError : jest . fn ( ) ,
132112} ) ) ;
@@ -262,59 +242,6 @@ describe('<CourseOutline />', () => {
262242 expect ( await findByText ( messages . alertSuccessDescription . defaultMessage ) ) . toBeInTheDocument ( ) ;
263243 } ) ;
264244
265- it ( 'check video sharing option udpates correctly' , async ( ) => {
266- const { findByLabelText } = renderComponent ( ) ;
267-
268- axiosMock
269- . onPost ( getCourseBlockApiUrl ( courseId ) , {
270- metadata : {
271- video_sharing_options : VIDEO_SHARING_OPTIONS . allOff ,
272- } ,
273- } )
274- . reply ( 200 ) ;
275- const optionDropdown = await findByLabelText ( statusBarMessages . videoSharingTitle . defaultMessage ) ;
276- await act (
277- async ( ) => fireEvent . change ( optionDropdown , { target : { value : VIDEO_SHARING_OPTIONS . allOff } } ) ,
278- ) ;
279-
280- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
281- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( {
282- metadata : {
283- video_sharing_options : VIDEO_SHARING_OPTIONS . allOff ,
284- } ,
285- } ) ) ;
286- } ) ;
287-
288- it ( 'check video sharing option shows error on failure' , async ( ) => {
289- renderComponent ( ) ;
290-
291- axiosMock
292- . onPost ( getCourseBlockApiUrl ( courseId ) , {
293- metadata : {
294- video_sharing_options : VIDEO_SHARING_OPTIONS . allOff ,
295- } ,
296- } )
297- . reply ( 500 ) ;
298- const optionDropdown = await screen . findByLabelText ( statusBarMessages . videoSharingTitle . defaultMessage ) ;
299- await act (
300- async ( ) => fireEvent . change ( optionDropdown , { target : { value : VIDEO_SHARING_OPTIONS . allOff } } ) ,
301- ) ;
302-
303- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
304- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( {
305- metadata : {
306- video_sharing_options : VIDEO_SHARING_OPTIONS . allOff ,
307- } ,
308- } ) ) ;
309-
310- const alertElements = screen . queryAllByRole ( 'alert' ) ;
311- expect ( alertElements . find (
312- ( el ) => el . classList . contains ( 'alert-content' ) ,
313- ) ) . toHaveTextContent (
314- 'Unable to save changes. Please try again.' ,
315- ) ;
316- } ) ;
317-
318245 it ( 'render error alert after failed reindex correctly' , async ( ) => {
319246 const { findByText, findByTestId } = renderComponent ( ) ;
320247
@@ -480,118 +407,66 @@ describe('<CourseOutline />', () => {
480407 } ) ;
481408
482409 it ( 'adds a unit from library correctly' , async ( ) => {
483- getContainerKey . mockReturnValue ( 'lct:org:lib:unit:1' ) ;
484- getContainerKey . mockReturnValue ( 'unit' ) ;
410+ const user = userEvent . setup ( ) ;
485411 renderComponent ( ) ;
486412 const [ sectionElement ] = await screen . findAllByTestId ( 'section-card' ) ;
487413 const [ subsectionElement ] = await within ( sectionElement ) . findAllByTestId ( 'subsection-card' ) ;
488414 const units = await within ( subsectionElement ) . findAllByTestId ( 'unit-card' ) ;
489415 expect ( units . length ) . toBe ( 1 ) ;
490416
491- axiosMock
492- . onPost ( postXBlockBaseApiUrl ( ) )
493- . reply ( 200 , {
494- locator : 'block-v1:UNIX+UX1+2025_T3+type@vertical+block@vertical1e842129' ,
495- parent_locator : 'parent' ,
496- } ) ;
497-
498417 const addUnitFromLibraryButton = within ( subsectionElement ) . getByRole ( 'button' , {
499418 name : / u s e u n i t f r o m l i b r a r y / i,
500419 } ) ;
501- fireEvent . click ( addUnitFromLibraryButton ) ;
502-
503- // click dummy button to execute onComponentSelected prop.
504- const dummyBtn = await screen . findByRole ( 'button' , { name : 'Dummy button' } ) ;
505- fireEvent . click ( dummyBtn ) ;
506-
507- await waitFor ( ( ) => expect ( axiosMock . history . post . length ) . toBe ( 3 ) ) ;
420+ await user . click ( addUnitFromLibraryButton ) ;
508421
422+ // Start the add existing unit flow
509423 const [ section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
510424 const [ subsection ] = section . childInfo . children ;
511- await waitFor ( ( ) => {
512- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( {
513- type : COMPONENT_TYPES . libraryV2 ,
514- category : 'vertical' ,
515- parent_locator : subsection . id ,
516- library_content_key : getContainerKey ( ) ,
517- } ) ) ;
425+ expect ( startCurrentFlow ) . toHaveBeenCalledWith ( {
426+ flowType : ContainerType . Unit ,
427+ parentLocator : subsection . id ,
428+ grandParentLocator : section . id ,
518429 } ) ;
519430 } ) ;
520431
521432 it ( 'adds a subsection from library correctly' , async ( ) => {
522- getContainerKey . mockReturnValue ( 'lct:org:lib:subsection:1' ) ;
523- getContainerKey . mockReturnValue ( 'subsection' ) ;
433+ const user = userEvent . setup ( ) ;
524434 renderComponent ( ) ;
525435 const [ sectionElement ] = await screen . findAllByTestId ( 'section-card' ) ;
526436 const subsections = await within ( sectionElement ) . findAllByTestId ( 'subsection-card' ) ;
527437 expect ( subsections . length ) . toBe ( 2 ) ;
528438
529- axiosMock
530- . onPost ( postXBlockBaseApiUrl ( ) )
531- . reply ( 200 , {
532- locator : 'block-v1:UNIX+UX1+2025_T3+type@sequential+block@sequential45d4d95a' ,
533- parent_locator : 'block-v1:UNIX+UX1+2025_T3+type@chapter+block@chaptersda1' ,
534- } ) ;
535-
536439 const addSubsectionFromLibraryButton = within ( sectionElement ) . getByRole ( 'button' , {
537440 name : / u s e s u b s e c t i o n f r o m l i b r a r y / i,
538441 } ) ;
539- fireEvent . click ( addSubsectionFromLibraryButton ) ;
540-
541- // click dummy button to execute onComponentSelected prop.
542- const dummyBtn = await screen . findByRole ( 'button' , { name : 'Dummy button' } ) ;
543- fireEvent . click ( dummyBtn ) ;
544-
545- await waitFor ( ( ) => expect ( axiosMock . history . post . length ) . toBe ( 3 ) ) ;
442+ await user . click ( addSubsectionFromLibraryButton ) ;
546443
444+ // Start the add existing subsection flow
547445 const [ section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
548- await waitFor ( ( ) => {
549- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( {
550- type : COMPONENT_TYPES . libraryV2 ,
551- category : 'sequential' ,
552- parent_locator : section . id ,
553- library_content_key : getContainerKey ( ) ,
554- } ) ) ;
446+ expect ( startCurrentFlow ) . toHaveBeenCalledWith ( {
447+ flowType : ContainerType . Subsection ,
448+ parentLocator : section . id ,
449+ grandParentLocator : undefined ,
555450 } ) ;
556451 } ) ;
557452
558453 it ( 'adds a section from library correctly' , async ( ) => {
559454 const user = userEvent . setup ( ) ;
560- getContainerKey . mockReturnValue ( 'lct:org:lib:section:1' ) ;
561- getContainerKey . mockReturnValue ( 'section' ) ;
562455 renderComponent ( ) ;
563456 const sections = await screen . findAllByTestId ( 'section-card' ) ;
564457 expect ( sections . length ) . toBe ( 4 ) ;
565458
566- axiosMock
567- . onPost ( postXBlockBaseApiUrl ( ) )
568- . reply ( 200 , {
569- locator : 'block-v1:UNIX+UX1+2025_T3+type@chapter+block@chaptersdafdd' ,
570- courseKey : 'course-v1:UNIX+UX1+2025_T3' ,
571- } ) ;
572- axiosMock
573- . onGet ( getXBlockApiUrl ( 'block-v1:UNIX+UX1+2025_T3+type@chapter+block@chaptersdafdd' ) )
574- . reply ( 200 , courseSectionMock ) ;
575-
576459 const addSectionFromLibraryButton = await screen . findByRole ( 'button' , {
577460 name : / u s e s e c t i o n f r o m l i b r a r y / i,
578461 } ) ;
579462 await user . click ( addSectionFromLibraryButton ) ;
580463
581- // click dummy button to execute onComponentSelected prop.
582- const dummyBtn = await screen . findByRole ( 'button' , { name : 'Dummy button' } ) ;
583- fireEvent . click ( dummyBtn ) ;
584-
585- await waitFor ( ( ) => expect ( axiosMock . history . post . length ) . toBe ( 3 ) ) ;
586-
587- const courseUsageKey = courseOutlineIndexMock . courseStructure . id ;
588- await waitFor ( ( ) => {
589- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( {
590- type : COMPONENT_TYPES . libraryV2 ,
591- category : 'chapter' ,
592- parent_locator : courseUsageKey ,
593- library_content_key : getContainerKey ( ) ,
594- } ) ) ;
464+ // Start the add existring section flow
465+ const courseBlockId = courseOutlineIndexMock . courseStructure . id ;
466+ expect ( startCurrentFlow ) . toHaveBeenCalledWith ( {
467+ flowType : ContainerType . Section ,
468+ parentLocator : courseBlockId ,
469+ grandParentLocator : undefined ,
595470 } ) ;
596471 } ) ;
597472
@@ -820,6 +695,7 @@ describe('<CourseOutline />', () => {
820695 } ) ;
821696
822697 it ( 'check whether section, subsection and unit is deleted when corresponding delete button is clicked' , async ( ) => {
698+ const user = userEvent . setup ( ) ;
823699 renderComponent ( ) ;
824700 // get section, subsection and unit
825701 const [ section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
@@ -831,22 +707,18 @@ describe('<CourseOutline />', () => {
831707 selectedContainerId = section . id ;
832708
833709 const checkDeleteBtn = async ( item , element , elementName ) => {
834- await waitFor ( ( ) => {
835- expect ( screen . queryByText ( item . displayName ) ) . toBeInTheDocument ( ) ;
836- } ) ;
710+ expect ( within ( element ) . getByText ( item . displayName ) ) . toBeInTheDocument ( ) ;
837711
838712 axiosMock . onDelete ( getCourseItemApiUrl ( item . id ) ) . reply ( 200 ) ;
839713
840714 const menu = await within ( element ) . findByTestId ( `${ elementName } -card-header__menu-button` ) ;
841- fireEvent . click ( menu ) ;
715+ await user . click ( menu ) ;
842716 const deleteButton = await within ( element ) . findByTestId ( `${ elementName } -card-header__menu-delete-button` ) ;
843- fireEvent . click ( deleteButton ) ;
717+ await user . click ( deleteButton ) ;
844718 const confirmButton = await screen . findByRole ( 'button' , { name : 'Delete' } ) ;
845- fireEvent . click ( confirmButton ) ;
719+ await user . click ( confirmButton ) ;
846720
847- await waitFor ( ( ) => {
848- expect ( screen . queryByText ( item . displayName ) ) . not . toBeInTheDocument ( ) ;
849- } ) ;
721+ expect ( element ) . not . toBeInTheDocument ( ) ;
850722 } ;
851723
852724 // delete unit, subsection and then section in order.
@@ -2614,10 +2486,6 @@ describe('<CourseOutline />', () => {
26142486 } ) ;
26152487
26162488 it ( 'check that the new status bar and expand bar is shown when flag is set' , async ( ) => {
2617- setConfig ( {
2618- ...getConfig ( ) ,
2619- ENABLE_COURSE_OUTLINE_NEW_DESIGN : 'true' ,
2620- } ) ;
26212489 renderComponent ( ) ;
26222490 const btn = await screen . findByRole ( 'button' , { name : 'Collapse all' } ) ;
26232491 expect ( btn ) . toBeInTheDocument ( ) ;
0 commit comments