@@ -41,6 +41,7 @@ import {
4141 courseBestPracticesMock ,
4242 courseLaunchMock ,
4343 buildTestOutline ,
44+ type NodeSpec ,
4445} from './__mocks__' ;
4546import { COURSE_BLOCK_NAMES , VIDEO_SHARING_OPTIONS } from './constants' ;
4647import CourseOutline from './CourseOutline' ;
@@ -71,7 +72,7 @@ const buildCourseOutlineIndexMock = () =>
7172 overrides : cloneDeep ( originalCourseOutlineIndexMock ) as Record < string , unknown > ,
7273 } ) as unknown as typeof originalCourseOutlineIndexMock ;
7374
74- let courseOutlineIndexMock = buildCourseOutlineIndexMock ( ) ;
75+ let courseOutlineIndexMock : any = buildCourseOutlineIndexMock ( ) ;
7576
7677// ─── Local snake_case API-response mocks ────────────────────────────────
7778const courseSectionMock = {
@@ -219,6 +220,118 @@ jest.mock('@src/studio-home/data/selectors', () => ({
219220// eslint-disable-next-line no-promise-executor-return
220221const sleep = ( ms ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
221222
223+ /**
224+ * Replace the global outline fixture with buildTestOutline result.
225+ * Reseeds React Query cache and updates the axios handler.
226+ * Call inside individual tests (after beforeEach has run).
227+ */
228+ function useTestOutline (
229+ arg ?: NodeSpec [ ] | { sections ?: NodeSpec [ ] ; overrides ?: Record < string , unknown > ; } ,
230+ ) {
231+ courseOutlineIndexMock = arg ? buildTestOutline ( arg ) : buildTestOutline ( ) ;
232+
233+ // Update the API handler to return the new data
234+ axiosMock . onGet ( getCourseOutlineIndexApiUrl ( courseId ) ) . reply ( 200 , courseOutlineIndexMock ) ;
235+
236+ // Reseed the index cache
237+ queryClient . setQueryData ( courseOutlineQueryKeys . index ( courseId ) , cloneDeep ( courseOutlineIndexMock ) ) ;
238+
239+ // Reseed item-level caches
240+ const children = ( courseOutlineIndexMock . courseStructure as any ) . childInfo . children ;
241+ children . forEach ( ( section : any ) => {
242+ queryClient . setQueryData ( courseOutlineQueryKeys . courseItemId ( section . id ) , section ) ;
243+ ( section . childInfo ?. children || [ ] ) . forEach ( ( subsection : any ) => {
244+ queryClient . setQueryData ( courseOutlineQueryKeys . courseItemId ( subsection . id ) , subsection ) ;
245+ ( subsection . childInfo ?. children || [ ] ) . forEach ( ( unit : any ) => {
246+ queryClient . setQueryData ( courseOutlineQueryKeys . courseItemId ( unit . id ) , unit ) ;
247+ } ) ;
248+ } ) ;
249+ } ) ;
250+ }
251+
252+ /** Build a 4-section NodeSpec[] for reorder/drag/move tests with valid block-v1 IDs. */
253+ function buildReorderOutlineSpec ( ) : NodeSpec [ ] {
254+ const id = ( type : string , block : string ) => `block-v1:edX+DemoX+Demo_Course+type@${ type } +block@${ block } ` ;
255+
256+ /** Match the old mock's IDs for section-0 so collision-based drag handlers work. */
257+ const oldSectionId = id ( 'chapter' , 'd8a6192ade314473a78242dfeedfbf5b' ) ;
258+ const oldSub0Id = id ( 'sequential' , '8a85e287e30a47e98d8c1f37f74a6a9d' ) ;
259+ const oldSub1Id = id ( 'sequential' , 'b713bc2830f34f6f87554028c3068729' ) ;
260+
261+ return [
262+ {
263+ id : oldSectionId ,
264+ displayName : 'Section 0' ,
265+ children : [
266+ {
267+ id : oldSub0Id ,
268+ displayName : 'S0 Sub 0' ,
269+ children : [ { id : id ( 'vertical' , 's0-sub-0-unit-0' ) , displayName : 'Unit' } ] ,
270+ } ,
271+ {
272+ id : oldSub1Id ,
273+ displayName : 'S0 Sub 1' ,
274+ children : [ { id : id ( 'vertical' , 's0-sub-1-unit-0' ) , displayName : 'Unit' } ] ,
275+ } ,
276+ ] ,
277+ } ,
278+ {
279+ id : id ( 'chapter' , 'section-1' ) ,
280+ displayName : 'Section 1' ,
281+ children : [
282+ {
283+ id : id ( 'sequential' , 's1-sub-0' ) ,
284+ displayName : 'S1 Sub 0' ,
285+ children : [ { id : id ( 'vertical' , 's1-sub-0-unit-0' ) , displayName : 'Unit' } ] ,
286+ } ,
287+ {
288+ id : id ( 'sequential' , 's1-sub-1' ) ,
289+ displayName : 'S1 Sub 1' ,
290+ children : [
291+ { id : id ( 'vertical' , 's1-sub-1-unit-0' ) , displayName : 'Unit' } ,
292+ { id : id ( 'vertical' , 's1-sub-1-unit-1' ) , displayName : 'Unit' } ,
293+ ] ,
294+ } ,
295+ ] ,
296+ } ,
297+ {
298+ id : id ( 'chapter' , 'section-2' ) ,
299+ displayName : 'Section 2' ,
300+ children : [
301+ {
302+ id : id ( 'sequential' , 's2-sub-0' ) ,
303+ displayName : 'S2 Sub 0' ,
304+ children : [
305+ { id : id ( 'vertical' , 's2-sub-0-unit-0' ) , displayName : 'Unit' } ,
306+ { id : id ( 'vertical' , 's2-sub-0-unit-1' ) , displayName : 'Unit' } ,
307+ ] ,
308+ } ,
309+ ] ,
310+ } ,
311+ {
312+ id : id ( 'chapter' , 'section-3' ) ,
313+ displayName : 'Section 3' ,
314+ children : [
315+ {
316+ id : id ( 'sequential' , 's3-sub-0' ) ,
317+ displayName : 'S3 Sub 0' ,
318+ children : [ { id : id ( 'vertical' , 's3-sub-0-unit-0' ) , displayName : 'Unit' } ] ,
319+ } ,
320+ ] ,
321+ } ,
322+ ] ;
323+ }
324+
325+ /** Wrapper around useTestOutline for reorder/move tests — overrides courseStructure.id to match old mock. */
326+ function useReorderTestOutline ( ) {
327+ useTestOutline ( {
328+ sections : buildReorderOutlineSpec ( ) ,
329+ overrides : {
330+ courseStructure : { id : 'block-v1:edX+DemoX+Demo_Course+type@course+block@course' } ,
331+ } ,
332+ } ) ;
333+ }
334+
222335const renderComponent = ( ) =>
223336 render (
224337 < CourseAuthoringProvider courseId = { courseId } >
@@ -298,6 +411,7 @@ describe('<CourseOutline />', () => {
298411 } ) ;
299412
300413 it ( 'render CourseOutline component correctly' , async ( ) => {
414+ useTestOutline ( { overrides : { courseStructure : { displayName : 'Demonstration Course' } } } ) ;
301415 renderComponent ( ) ;
302416
303417 expect ( await screen . findByText ( 'Demonstration Course' ) ) . toBeInTheDocument ( ) ;
@@ -307,6 +421,7 @@ describe('<CourseOutline />', () => {
307421 it ( 'renders sections from React Query without pre-loading Redux (page refresh scenario)' , async ( ) => {
308422 // Create fresh mock state — no pre-loaded Redux data, empty React Query cache.
309423 ( { axiosMock, queryClient } = initializeMocks ( ) ) ;
424+ useTestOutline ( ) ;
310425 axiosMock
311426 . onGet ( getCourseOutlineIndexApiUrl ( courseId ) )
312427 . reply ( 200 , courseOutlineIndexMock ) ;
@@ -364,6 +479,7 @@ describe('<CourseOutline />', () => {
364479 } ) ;
365480
366481 it ( 'check reindex and render success alert is correctly' , async ( ) => {
482+ useTestOutline ( { overrides : { reindexLink : '/course/course-v1:edX+DemoX+Demo_Course/search_reindex' } } ) ;
367483 const { findByText, findByTestId } = renderComponent ( ) ;
368484
369485 axiosMock
@@ -439,6 +555,7 @@ describe('<CourseOutline />', () => {
439555 } ) ;
440556
441557 it ( 'render error alert after failed reindex correctly' , async ( ) => {
558+ useTestOutline ( { overrides : { reindexLink : '/course/course-v1:edX+DemoX+Demo_Course/search_reindex' } } ) ;
442559 const { findByText, findByTestId } = renderComponent ( ) ;
443560
444561 axiosMock
@@ -451,6 +568,7 @@ describe('<CourseOutline />', () => {
451568 } ) ;
452569
453570 it ( 'check that new section list is saved when dragged' , async ( ) => {
571+ useReorderTestOutline ( ) ;
454572 const { findAllByRole, findByTestId } = renderComponent ( ) ;
455573 const expandAllButton = await findByTestId ( 'expand-collapse-all-button' ) ;
456574 fireEvent . click ( expandAllButton ) ;
@@ -495,6 +613,7 @@ describe('<CourseOutline />', () => {
495613 } ) ;
496614
497615 it ( 'check section list is restored to original order when API call fails' , async ( ) => {
616+ useReorderTestOutline ( ) ;
498617 const { findAllByRole, findByTestId } = renderComponent ( ) ;
499618 const expandAllButton = await findByTestId ( 'expand-collapse-all-button' ) ;
500619 fireEvent . click ( expandAllButton ) ;
@@ -687,13 +806,15 @@ describe('<CourseOutline />', () => {
687806 } ) ;
688807
689808 it ( 'render checklist value correctly' , async ( ) => {
809+ useTestOutline ( ) ;
690810 const { findByText } = renderComponent ( ) ;
691811
692812 // Data is loaded via mount effects; wait for checklist to appear
693813 expect ( await findByText ( '3/8 completed' ) ) . toBeInTheDocument ( ) ;
694814 } ) ;
695815
696816 it ( 'render alerts if checklist api fails' , async ( ) => {
817+ useTestOutline ( ) ;
697818 axiosMock
698819 . onGet ( getCourseLaunchApiUrl ( {
699820 courseId,
@@ -715,6 +836,7 @@ describe('<CourseOutline />', () => {
715836 } ) ;
716837
717838 it ( 'check highlights are enabled after enable highlights query is successful' , async ( ) => {
839+ useTestOutline ( ) ;
718840 const { findByTestId, findByText } = renderComponent ( ) ;
719841
720842 axiosMock . reset ( ) ;
@@ -744,6 +866,7 @@ describe('<CourseOutline />', () => {
744866 } ) ;
745867
746868 it ( 'should expand and collapse subsections, after click on subheader buttons' , async ( ) => {
869+ useTestOutline ( ) ;
747870 const { queryAllByTestId, findByText } = renderComponent ( ) ;
748871
749872 const collapseBtn = await findByText ( headerMessages . collapseAllButton . defaultMessage ) ;
@@ -777,6 +900,7 @@ describe('<CourseOutline />', () => {
777900 } ) ;
778901
779902 it ( 'render configuration alerts and check dismiss query' , async ( ) => {
903+ useTestOutline ( ) ;
780904 axiosMock
781905 . onGet ( getCourseOutlineIndexApiUrl ( courseId ) )
782906 . reply ( 200 , {
@@ -1946,6 +2070,7 @@ describe('<CourseOutline />', () => {
19462070 } ) ;
19472071
19482072 it ( 'check whether section move up and down options work correctly' , async ( ) => {
2073+ useReorderTestOutline ( ) ;
19492074 const { findAllByTestId } = renderComponent ( ) ;
19502075 // get second section element
19512076 const courseBlockId = courseOutlineIndexMock . courseStructure . id ;
@@ -1982,6 +2107,7 @@ describe('<CourseOutline />', () => {
19822107 } ) ;
19832108
19842109 it ( 'check whether section move up & down option is rendered correctly based on index' , async ( ) => {
2110+ useReorderTestOutline ( ) ;
19852111 const { findAllByTestId } = renderComponent ( ) ;
19862112 // get first, second and last section element
19872113 const {
@@ -2028,6 +2154,7 @@ describe('<CourseOutline />', () => {
20282154 } ) ;
20292155
20302156 it ( 'check whether subsection move up and down options work correctly' , async ( ) => {
2157+ useReorderTestOutline ( ) ;
20312158 const { findAllByTestId } = renderComponent ( ) ;
20322159 // get second section element
20332160 const [ section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
@@ -2078,6 +2205,7 @@ describe('<CourseOutline />', () => {
20782205 } ) ;
20792206
20802207 it ( 'check whether subsection move up to prev section if it is on top of its parent section' , async ( ) => {
2208+ useReorderTestOutline ( ) ;
20812209 const { findAllByTestId } = renderComponent ( ) ;
20822210 const [ firstSection , section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
20832211 const [ , sectionElement ] = await findAllByTestId ( 'section-card' ) ;
@@ -2122,6 +2250,7 @@ describe('<CourseOutline />', () => {
21222250 } ) ;
21232251
21242252 it ( 'check whether subsection move down to next section if it is in bottom position of its parent section' , async ( ) => {
2253+ useReorderTestOutline ( ) ;
21252254 const { findAllByTestId } = renderComponent ( ) ;
21262255 const [ section , secondSection ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
21272256 const [ sectionElement ] = await findAllByTestId ( 'section-card' ) ;
@@ -2167,6 +2296,7 @@ describe('<CourseOutline />', () => {
21672296 } ) ;
21682297
21692298 it ( 'check whether subsection move up & down option is rendered correctly based on index' , async ( ) => {
2299+ useReorderTestOutline ( ) ;
21702300 const { findAllByTestId } = renderComponent ( ) ;
21712301 // using first section
21722302 const sectionElements = await findAllByTestId ( 'section-card' ) ;
@@ -2217,6 +2347,7 @@ describe('<CourseOutline />', () => {
22172347 } ) ;
22182348
22192349 it ( 'check whether unit move up and down options work correctly' , async ( ) => {
2350+ useReorderTestOutline ( ) ;
22202351 const { findAllByTestId } = renderComponent ( ) ;
22212352 // get second section -> second subsection -> second unit element
22222353 const [ , section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
@@ -2270,6 +2401,7 @@ describe('<CourseOutline />', () => {
22702401 } ) ;
22712402
22722403 it ( 'check whether unit moves up to previous subsection if it is in top position in parent subsection' , async ( ) => {
2404+ useReorderTestOutline ( ) ;
22732405 const { findAllByTestId } = renderComponent ( ) ;
22742406 // get second section -> second subsection -> first unit element
22752407 const [ , section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
@@ -2317,6 +2449,7 @@ describe('<CourseOutline />', () => {
23172449 } ) ;
23182450
23192451 it ( 'check whether unit moves up to previous subsection of prev section if it is in top position in parent subsection & section' , async ( ) => {
2452+ useReorderTestOutline ( ) ;
23202453 const { findAllByTestId } = renderComponent ( ) ;
23212454 // get second section -> second subsection -> first unit element
23222455 const [ firstSection , secondSection ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
@@ -2366,6 +2499,7 @@ describe('<CourseOutline />', () => {
23662499 } ) ;
23672500
23682501 it ( 'check whether unit moves down to next subsection if it is in last position in parent subsection' , async ( ) => {
2502+ useReorderTestOutline ( ) ;
23692503 const { findAllByTestId } = renderComponent ( ) ;
23702504 // get second section -> second subsection -> first unit element
23712505 const [ , section ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
@@ -2414,6 +2548,7 @@ describe('<CourseOutline />', () => {
24142548 } ) ;
24152549
24162550 it ( 'check whether unit moves down to next subsection of next section if it is in last position in parent subsection & section' , async ( ) => {
2551+ useReorderTestOutline ( ) ;
24172552 const { findAllByTestId } = renderComponent ( ) ;
24182553 // get second section -> second subsection -> first unit element
24192554 const [ , secondSection , thirdSection ] = courseOutlineIndexMock . courseStructure . childInfo . children ;
@@ -2466,6 +2601,7 @@ describe('<CourseOutline />', () => {
24662601 } ) ;
24672602
24682603 it ( 'check whether unit move up & down option is rendered correctly based on index' , async ( ) => {
2604+ useReorderTestOutline ( ) ;
24692605 const { findAllByTestId } = renderComponent ( ) ;
24702606 // using first section -> first subsection -> first unit
24712607 const sections = await findAllByTestId ( 'section-card' ) ;
@@ -2595,6 +2731,7 @@ describe('<CourseOutline />', () => {
25952731 } ) ;
25962732
25972733 it ( 'check that new unit list is saved when dragged' , async ( ) => {
2734+ useReorderTestOutline ( ) ;
25982735 const { findAllByTestId } = renderComponent ( ) ;
25992736 // get third section
26002737 const [ , , sectionElement ] = await findAllByTestId ( 'section-card' ) ;
@@ -2636,6 +2773,7 @@ describe('<CourseOutline />', () => {
26362773 } ) ;
26372774
26382775 it ( 'check that new unit list is restored to original order when API call fails' , async ( ) => {
2776+ useReorderTestOutline ( ) ;
26392777 const { findAllByTestId } = renderComponent ( ) ;
26402778 // get third section
26412779 const [ , , sectionElement ] = await findAllByTestId ( 'section-card' ) ;
0 commit comments