Skip to content

Commit 1c65a2e

Browse files
committed
test(course-outline): migrate outline tests to factory fixtures
1 parent 9f3a99b commit 1c65a2e

1 file changed

Lines changed: 139 additions & 1 deletion

File tree

src/course-outline/CourseOutline.test.tsx

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
courseBestPracticesMock,
4242
courseLaunchMock,
4343
buildTestOutline,
44+
type NodeSpec,
4445
} from './__mocks__';
4546
import { COURSE_BLOCK_NAMES, VIDEO_SHARING_OPTIONS } from './constants';
4647
import 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 ────────────────────────────────
7778
const courseSectionMock = {
@@ -219,6 +220,118 @@ jest.mock('@src/studio-home/data/selectors', () => ({
219220
// eslint-disable-next-line no-promise-executor-return
220221
const 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+
222335
const 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

Comments
 (0)