@@ -363,6 +363,91 @@ function useOperationsTestOutline() {
363363 } ) ;
364364}
365365
366+ /** Build a 2-section NodeSpec for configure modal tests. */
367+ function buildConfigureOutlineSpec ( ) : NodeSpec [ ] {
368+ const id = ( type : string , block : string ) => `block-v1:edX+DemoX+Demo_Course+type@${ type } +block@${ block } ` ;
369+ return [
370+ {
371+ id : id ( 'chapter' , 'd8a6192ade314473a78242dfeedfbf5b' ) ,
372+ displayName : 'Introduction 12' ,
373+ overrides : {
374+ start : '2023-08-10T22:00:00Z' ,
375+ visibilityState : 'staff_only' ,
376+ published : false ,
377+ courseGraders : [ 'Homework' , 'Exam' ] ,
378+ } ,
379+ children : [
380+ {
381+ id : id ( 'sequential' , '8a85e287e30a47e98d8c1f37f74a6a9d' ) ,
382+ displayName : 'Subsection 1A' ,
383+ overrides : {
384+ start : '1970-01-01T05:00:00Z' ,
385+ visibilityState : 'draft' ,
386+ published : false ,
387+ courseGraders : [ 'Homework' , 'Exam' ] ,
388+ isTimeLimited : false ,
389+ isProctoredExam : false ,
390+ isOnboardingExam : false ,
391+ isPracticeExam : false ,
392+ examReviewRules : '' ,
393+ defaultTimeLimitMinutes : null ,
394+ hideAfterDue : false ,
395+ wasExamEverLinkedWithExternal : false ,
396+ onlineProctoringRules : '' ,
397+ supportsOnboarding : false ,
398+ showReviewRules : true ,
399+ isPrereq : false ,
400+ prereqs : [ {
401+ blockUsageKey : id ( 'sequential' , 'b713bc2830f34f6f87554028c3068729' ) ,
402+ blockDisplayName : 'Subsection 1B' ,
403+ } ] ,
404+ } ,
405+ children : [ { id : id ( 'vertical' , '0f652012aa294ed9b4360a1e4f6c5232' ) , displayName : 'Unit 1A1' } ] ,
406+ } ,
407+ {
408+ id : id ( 'sequential' , 'b713bc2830f34f6f87554028c3068729' ) ,
409+ displayName : 'Subsection 1B' ,
410+ overrides : {
411+ start : '2013-02-05T05:00:00Z' ,
412+ visibilityState : 'live' ,
413+ published : true ,
414+ courseGraders : [ 'Homework' , 'Exam' ] ,
415+ } ,
416+ children : [ { id : id ( 'vertical' , 'sec1-sub1-unit-0' ) , displayName : 'Unit' } ] ,
417+ } ,
418+ ] ,
419+ } ,
420+ {
421+ id : id ( 'chapter' , 'section-2' ) ,
422+ displayName : 'Section 2' ,
423+ overrides : { visibilityState : 'live' } ,
424+ children : [
425+ {
426+ id : id ( 'sequential' , 'sec2-sub-0' ) ,
427+ displayName : 'Sec2 Sub 0' ,
428+ overrides : { courseGraders : [ 'Homework' , 'Exam' ] } ,
429+ children : [ { id : id ( 'vertical' , 'sec2-sub-0-unit-0' ) , displayName : 'Unit' } ] ,
430+ } ,
431+ ] ,
432+ } ,
433+ ] ;
434+ }
435+
436+ function useConfigureTestOutline ( ) {
437+ useTestOutline ( {
438+ sections : buildConfigureOutlineSpec ( ) ,
439+ overrides : {
440+ createdOn : new Date ( ) . toISOString ( ) ,
441+ courseStructure : {
442+ id : 'block-v1:edX+DemoX+Demo_Course+type@course+block@course' ,
443+ displayName : 'Demonstration Course' ,
444+ enableProctoredExams : true ,
445+ enableTimedExams : true ,
446+ } ,
447+ } ,
448+ } ) ;
449+ }
450+
366451/** Wrapper around useTestOutline for reorder/move tests — overrides courseStructure.id to match old mock. */
367452function useReorderTestOutline ( ) {
368453 useTestOutline ( {
@@ -760,6 +845,7 @@ describe('<CourseOutline />', () => {
760845 } ) ;
761846
762847 it ( 'adds new unit correctly' , async ( ) => {
848+ useTestOutline ( ) ;
763849 const { findAllByTestId } = renderComponent ( ) ;
764850 const [ sectionElement ] = await findAllByTestId ( 'section-card' ) ;
765851 const [ subsectionElement ] = await within ( sectionElement ) . findAllByTestId ( 'subsection-card' ) ;
@@ -773,15 +859,19 @@ describe('<CourseOutline />', () => {
773859 } ) ;
774860 const newUnitButton = await within ( subsectionElement ) . findByRole ( 'button' , { name : 'New unit' } ) ;
775861 await act ( async ( ) => fireEvent . click ( newUnitButton ) ) ;
776- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
862+
777863 const [ section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
778864 const [ subsection ] = section . childInfo . children ;
779- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( {
865+ const newUnitPost = axiosMock . history . post . find (
866+ ( entry : any ) => entry . data && entry . data . includes ( COURSE_BLOCK_NAMES . vertical . id ) ,
867+ ) ;
868+ expect ( newUnitPost ) . toBeDefined ( ) ;
869+ expect ( JSON . parse ( newUnitPost ! . data ) ) . toEqual ( {
780870 type : COURSE_BLOCK_NAMES . vertical . id ,
781871 category : COURSE_BLOCK_NAMES . vertical . id ,
782872 parent_locator : subsection . id ,
783873 display_name : COURSE_BLOCK_NAMES . vertical . name ,
784- } ) ) ;
874+ } ) ;
785875 } ) ;
786876
787877 it ( 'adds a unit from library correctly' , async ( ) => {
@@ -974,6 +1064,7 @@ describe('<CourseOutline />', () => {
9741064
9751065 it ( 'check edit title works for section, subsection and unit' , async ( ) => {
9761066 const user = userEvent . setup ( ) ;
1067+ useOperationsTestOutline ( ) ;
9771068 renderComponent ( ) ;
9781069 const [ section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
9791070 const checkEditTitle = async ( element , item , newName , elementName ) => {
@@ -1304,6 +1395,7 @@ describe('<CourseOutline />', () => {
13041395 } ) ;
13051396
13061397 it ( 'check configure modal for section' , async ( ) => {
1398+ useConfigureTestOutline ( ) ;
13071399 const { findByTestId, findAllByTestId } = renderComponent ( ) ;
13081400 const section = courseOutlineIndexMock . courseStructure . childInfo . children [ 0 ] ;
13091401 const newReleaseDateIso = '2025-09-10T22:00:00Z' ;
@@ -1341,14 +1433,17 @@ describe('<CourseOutline />', () => {
13411433 const saveButton = await findByTestId ( 'configure-save-button' ) ;
13421434 await act ( async ( ) => fireEvent . click ( saveButton ) ) ;
13431435
1344- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
1345- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( {
1436+ const sectionCfgPost = axiosMock . history . post . find (
1437+ ( entry : any ) => entry . data && entry . data . includes ( 'visible_to_staff_only' ) ,
1438+ ) ;
1439+ expect ( sectionCfgPost ) . toBeDefined ( ) ;
1440+ expect ( JSON . parse ( sectionCfgPost ! . data ) ) . toEqual ( {
13461441 publish : 'republish' ,
13471442 metadata : {
13481443 visible_to_staff_only : true ,
13491444 start : newReleaseDateIso ,
13501445 } ,
1351- } ) ) ;
1446+ } ) ;
13521447
13531448 await act ( async ( ) => fireEvent . click ( sectionDropdownButton ) ) ;
13541449 await act ( async ( ) => fireEvent . click ( configureBtn ) ) ;
@@ -1358,6 +1453,7 @@ describe('<CourseOutline />', () => {
13581453 } ) ;
13591454
13601455 it ( 'check configure modal for subsection' , async ( ) => {
1456+ useConfigureTestOutline ( ) ;
13611457 const user = userEvent . setup ( ) ;
13621458 const {
13631459 findAllByTestId,
@@ -1446,8 +1542,11 @@ describe('<CourseOutline />', () => {
14461542 await user . click ( saveButton ) ;
14471543
14481544 // verify request
1449- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
1450- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( expectedRequestData ) ) ;
1545+ const subCfgPost = axiosMock . history . post . find (
1546+ ( entry : any ) => entry . data && entry . data . includes ( expectedRequestData . graderType ) ,
1547+ ) ;
1548+ expect ( subCfgPost ) . toBeDefined ( ) ;
1549+ expect ( JSON . parse ( subCfgPost ! . data ) ) . toEqual ( expectedRequestData ) ;
14511550
14521551 // reopen modal and check values
14531552 await user . click ( subsectionDropdownButton ) ;
@@ -1480,6 +1579,7 @@ describe('<CourseOutline />', () => {
14801579 } ) ;
14811580
14821581 it ( 'check prereq and proctoring settings in configure modal for subsection' , async ( ) => {
1582+ useConfigureTestOutline ( ) ;
14831583 const user = userEvent . setup ( ) ;
14841584 const {
14851585 findAllByTestId,
@@ -1587,8 +1687,11 @@ describe('<CourseOutline />', () => {
15871687 await user . click ( saveButton ) ;
15881688
15891689 // verify request
1590- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
1591- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( expectedRequestData ) ) ;
1690+ const cfgPosta = axiosMock . history . post . find (
1691+ ( entry : any ) => entry . data && entry . data . includes ( expectedRequestData . graderType ) ,
1692+ ) ;
1693+ expect ( cfgPosta ) . toBeDefined ( ) ;
1694+ expect ( JSON . parse ( cfgPosta ! . data ) ) . toEqual ( expectedRequestData ) ;
15921695
15931696 // reopen modal and check values
15941697 await user . click ( subsectionDropdownButton ) ;
@@ -1628,6 +1731,7 @@ describe('<CourseOutline />', () => {
16281731 } ) ;
16291732
16301733 it ( 'check practice proctoring settings in configure modal' , async ( ) => {
1734+ useConfigureTestOutline ( ) ;
16311735 const user = userEvent . setup ( ) ;
16321736 const {
16331737 findAllByTestId,
@@ -1712,8 +1816,11 @@ describe('<CourseOutline />', () => {
17121816 await user . click ( saveButton ) ;
17131817
17141818 // verify request
1715- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
1716- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( expectedRequestData ) ) ;
1819+ const cfgPostb = axiosMock . history . post . find (
1820+ ( entry : any ) => entry . data && entry . data . includes ( expectedRequestData . graderType ) ,
1821+ ) ;
1822+ expect ( cfgPostb ) . toBeDefined ( ) ;
1823+ expect ( JSON . parse ( cfgPostb ! . data ) ) . toEqual ( expectedRequestData ) ;
17171824
17181825 // reopen modal and check values
17191826 await user . click ( subsectionDropdownButton ) ;
@@ -1820,8 +1927,11 @@ describe('<CourseOutline />', () => {
18201927 await user . click ( saveButton ) ;
18211928
18221929 // verify request
1823- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
1824- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( expectedRequestData ) ) ;
1930+ const cfgPostc = axiosMock . history . post . find (
1931+ ( entry : any ) => entry . data && entry . data . includes ( expectedRequestData . graderType ) ,
1932+ ) ;
1933+ expect ( cfgPostc ) . toBeDefined ( ) ;
1934+ expect ( JSON . parse ( cfgPostc ! . data ) ) . toEqual ( expectedRequestData ) ;
18251935
18261936 // reopen modal and check values
18271937 await user . click ( subsectionDropdownButton ) ;
@@ -1925,8 +2035,11 @@ describe('<CourseOutline />', () => {
19252035 await user . click ( saveButton ) ;
19262036
19272037 // verify request
1928- expect ( axiosMock . history . post . length ) . toBe ( 3 ) ;
1929- expect ( axiosMock . history . post [ 2 ] . data ) . toBe ( JSON . stringify ( expectedRequestData ) ) ;
2038+ const noSpecialPost = axiosMock . history . post . find (
2039+ ( entry : any ) => entry . data && entry . data . includes ( expectedRequestData . graderType ) ,
2040+ ) ;
2041+ expect ( noSpecialPost ) . toBeDefined ( ) ;
2042+ expect ( JSON . parse ( noSpecialPost ! . data ) ) . toEqual ( expectedRequestData ) ;
19302043
19312044 // Seed subsection cache + mock parent section GET so invalidateParentQueries
19322045 // refetch succeeds and reopened modal gets correct form values.
@@ -2073,9 +2186,19 @@ describe('<CourseOutline />', () => {
20732186
20742187 it ( 'check update highlights when update highlights query is successfully' , async ( ) => {
20752188 const user = userEvent . setup ( ) ;
2189+ useTestOutline ( {
2190+ sections : buildOperationsOutlineSpec ( ) ,
2191+ overrides : {
2192+ courseStructure : { id : 'block-v1:edX+DemoX+Demo_Course+type@course+block@course' } ,
2193+ } ,
2194+ } ) ;
2195+ // Seed highlights on section 0 so the highlights button appears.
2196+ const [ section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
2197+ section . highlights = [ 'Existing Highlight' ] ;
2198+ queryClient . setQueryData ( courseOutlineQueryKeys . courseItemId ( section . id ) , section ) ;
2199+
20762200 renderComponent ( ) ;
20772201
2078- const section = courseOutlineIndexMock . courseStructure . childInfo . children [ 0 ] ;
20792202 const highlights = [
20802203 'New Highlight 1' ,
20812204 'New Highlight 2' ,
0 commit comments