Skip to content

Commit d5d4160

Browse files
committed
fix(course-outline): preserve sidebar indices on back nav
1 parent 2399ea5 commit d5d4160

6 files changed

Lines changed: 108 additions & 11 deletions

File tree

src/course-outline/outline-sidebar/AddSidebar.test.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from '@src/course-outline/outline-sidebar/OutlineSidebarContext';
2222
import fetchMock from 'fetch-mock-jest';
2323
import type { ContainerType } from '@src/generic/key-utils';
24-
import { XBlock } from '@src/data/types';
24+
import { SelectionState, XBlock } from '@src/data/types';
2525
import { CourseAuthoringProvider } from '@src/CourseAuthoringContext';
2626
import { CourseOutlineProvider } from '@src/course-outline/CourseOutlineContext';
2727
import { snakeCaseKeys } from '@src/editors/utils';
@@ -70,17 +70,21 @@ jest.mock('@src/studio-home/hooks', () => ({
7070
let currentFlow: OutlineFlow | null = null;
7171
let isCurrentFlowOn = false;
7272
let currentItemData: Partial<XBlock> | null;
73+
let selectedContainerState: SelectionState | undefined;
7374
const clearSelection = jest.fn();
7475
const stopCurrentFlow = jest.fn();
76+
const openContainerSidebar = jest.fn();
7577
jest.mock('../outline-sidebar/OutlineSidebarContext', () => ({
7678
...jest.requireActual('../outline-sidebar/OutlineSidebarContext'),
7779
useOutlineSidebarContext: () => ({
7880
...jest.requireActual('../outline-sidebar/OutlineSidebarContext').useOutlineSidebarContext(),
7981
currentFlow,
8082
isCurrentFlowOn,
8183
currentItemData,
84+
selectedContainerState,
8285
clearSelection,
8386
stopCurrentFlow,
87+
openContainerSidebar,
8488
}),
8589
}));
8690

@@ -132,6 +136,10 @@ describe('AddSidebar', () => {
132136
return newMockResult;
133137
});
134138
outlineChildren = courseOutlineIndexMock.courseStructure.childInfo.children;
139+
selectedContainerState = undefined;
140+
clearSelection.mockClear();
141+
stopCurrentFlow.mockClear();
142+
openContainerSidebar.mockClear();
135143
});
136144

137145
it('renders the AddSidebar component without any errors', async () => {
@@ -399,7 +407,29 @@ describe('AddSidebar', () => {
399407

400408
const back = await screen.findByRole('button', { name: 'Back' });
401409
await user.click(back);
402-
expect(clearSelection).toHaveBeenCalled();
403410
expect(stopCurrentFlow).toHaveBeenCalled();
404411
});
412+
413+
it('back from unit sets subsection index in selection', async () => {
414+
const user = userEvent.setup();
415+
const section = outlineChildren[0];
416+
const subsection = section.childInfo.children[0];
417+
const unit = subsection.childInfo.children[0];
418+
selectedContainerState = {
419+
currentId: unit.id,
420+
subsectionId: subsection.id,
421+
sectionId: section.id,
422+
};
423+
renderComponent();
424+
425+
const back = await screen.findByRole('button', { name: 'Back' });
426+
await user.click(back);
427+
428+
expect(openContainerSidebar).toHaveBeenCalledWith(
429+
subsection.id,
430+
subsection.id,
431+
section.id,
432+
0,
433+
);
434+
});
405435
});

src/course-outline/outline-sidebar/AddSidebar.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ const AddTabs = () => {
359359
export const AddSidebar = () => {
360360
const intl = useIntl();
361361
const { courseDetails } = useCourseAuthoringContext();
362+
const { sections } = useCourseOutlineContext();
362363
const {
363364
isCurrentFlowOn,
364365
currentFlow,
@@ -395,9 +396,13 @@ export const AddSidebar = () => {
395396
}
396397

397398
const { currentId, subsectionId, sectionId } = selectedContainerState;
399+
const sectionIndex = sections.findIndex((section) => section.id === sectionId);
400+
const subsectionIndex = sections
401+
.find((section) => section.id === sectionId)
402+
?.childInfo.children.findIndex((subsection) => subsection.id === subsectionId) ?? -1;
398403

399404
if (currentId === subsectionId && sectionId) {
400-
openContainerSidebar(sectionId, undefined, sectionId);
405+
openContainerSidebar(sectionId, undefined, sectionId, sectionIndex >= 0 ? sectionIndex : undefined);
401406
return;
402407
}
403408

@@ -407,7 +412,12 @@ export const AddSidebar = () => {
407412
}
408413

409414
if (subsectionId) {
410-
openContainerSidebar(subsectionId, subsectionId, sectionId);
415+
openContainerSidebar(
416+
subsectionId,
417+
subsectionId,
418+
sectionId,
419+
subsectionIndex >= 0 ? subsectionIndex : undefined,
420+
);
411421
return;
412422
}
413423

src/course-outline/outline-sidebar/OutlineAlignSidebar.test.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ describe('OutlineAlignSidebar', () => {
3434
.spyOn(CourseOutlineContext, 'useCourseOutlineContext')
3535
.mockReturnValue({
3636
setCurrentSelection,
37+
sections: [
38+
{
39+
id: 'section-1',
40+
childInfo: {
41+
children: [
42+
{ id: 'subsection-1', childInfo: { children: [{ id: 'unit-1' }] } },
43+
],
44+
},
45+
},
46+
],
3747
} as any);
3848
jest
3949
.spyOn(OutlineSidebarContext, 'useOutlineSidebarContext')
@@ -112,11 +122,12 @@ describe('OutlineAlignSidebar', () => {
112122
const backButton = await screen.findByRole('button', { name: /back/i });
113123
backButton.click();
114124

115-
expect(openContainerSidebar).toHaveBeenCalledWith('subsection-1', 'subsection-1', 'section-1');
125+
expect(openContainerSidebar).toHaveBeenCalledWith('subsection-1', 'subsection-1', 'section-1', 0);
116126
expect(setCurrentSelection).toHaveBeenCalledWith({
117127
currentId: 'subsection-1',
118128
subsectionId: 'subsection-1',
119129
sectionId: 'section-1',
130+
index: 0,
120131
});
121132
});
122133
});

src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useOutlineSidebarContext } from './OutlineSidebarContext';
99
*/
1010
export const OutlineAlignSidebar = () => {
1111
const { courseId } = useCourseAuthoringContext();
12-
const { setCurrentSelection } = useCourseOutlineContext();
12+
const { setCurrentSelection, sections } = useCourseOutlineContext();
1313
const { selectedContainerState, clearSelection, openContainerSidebar } = useOutlineSidebarContext();
1414

1515
const sidebarContentId = selectedContainerState?.currentId || courseId;
@@ -18,6 +18,10 @@ export const OutlineAlignSidebar = () => {
1818

1919
const handleBack = () => {
2020
const { currentId, subsectionId, sectionId } = selectedContainerState || {};
21+
const sectionIndex = sections.findIndex((section) => section.id === sectionId);
22+
const subsectionIndex = sections
23+
.find((section) => section.id === sectionId)
24+
?.childInfo.children.findIndex((subsection) => subsection.id === subsectionId) ?? -1;
2125

2226
if (!currentId) {
2327
clearSelection();
@@ -26,8 +30,12 @@ export const OutlineAlignSidebar = () => {
2630
}
2731

2832
if (currentId === subsectionId && sectionId) {
29-
openContainerSidebar(sectionId, undefined, sectionId);
30-
setCurrentSelection({ currentId: sectionId, sectionId });
33+
openContainerSidebar(sectionId, undefined, sectionId, sectionIndex >= 0 ? sectionIndex : undefined);
34+
setCurrentSelection({
35+
currentId: sectionId,
36+
sectionId,
37+
index: sectionIndex >= 0 ? sectionIndex : undefined,
38+
});
3139
return;
3240
}
3341

@@ -38,8 +46,18 @@ export const OutlineAlignSidebar = () => {
3846
}
3947

4048
if (subsectionId) {
41-
openContainerSidebar(subsectionId, subsectionId, sectionId);
42-
setCurrentSelection({ currentId: subsectionId, subsectionId, sectionId });
49+
openContainerSidebar(
50+
subsectionId,
51+
subsectionId,
52+
sectionId,
53+
subsectionIndex >= 0 ? subsectionIndex : undefined,
54+
);
55+
setCurrentSelection({
56+
currentId: subsectionId,
57+
subsectionId,
58+
sectionId,
59+
index: subsectionIndex >= 0 ? subsectionIndex : undefined,
60+
});
4361
return;
4462
}
4563

src/course-outline/outline-sidebar/info-sidebar/InfoSidebar.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jest.mock('@src/course-outline/outline-sidebar/OutlineSidebarContext', () => ({
1515
selectedContainerState,
1616
clearSelection: jest.fn(),
1717
setSelectedContainerState: mockSetSelectedContainerState,
18+
openContainerInfoSidebar: mockOpenContainerInfoSidebar,
1819
}),
1920
}));
2021

@@ -39,6 +40,7 @@ const updateUnitOrderByIndex = jest.fn();
3940
const updateSubsectionOrderByIndex = jest.fn();
4041
const updateSectionOrderByIndex = jest.fn();
4142
const mockSetSelectedContainerState = jest.fn();
43+
const mockOpenContainerInfoSidebar = jest.fn();
4244
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4345
let mockSections: any[] = [];
4446

@@ -91,6 +93,7 @@ describe('InfoSidebar component', () => {
9193
updateSubsectionOrderByIndex.mockClear();
9294
updateSectionOrderByIndex.mockClear();
9395
mockSetSelectedContainerState.mockClear();
96+
mockOpenContainerInfoSidebar.mockClear();
9497
mockSections = [];
9598
});
9699

@@ -453,6 +456,26 @@ describe('InfoSidebar component', () => {
453456
await screen.findByRole('button', { name: 'Item Menu' });
454457
};
455458

459+
it('goes back to parent section with section index', async () => {
460+
const user = userEvent.setup();
461+
mockSections = [{
462+
id: 'block-v1:UNIX+UX1+2025_T3+type@chapter+block@ch1',
463+
actions: { childAddable: true },
464+
upstreamInfo: null,
465+
childInfo: { children: [] },
466+
}];
467+
await renderSubsectionMenu();
468+
469+
await user.click(screen.getByRole('button', { name: /back/i }));
470+
471+
expect(mockOpenContainerInfoSidebar).toHaveBeenCalledWith(
472+
'block-v1:UNIX+UX1+2025_T3+type@chapter+block@ch1',
473+
undefined,
474+
'block-v1:UNIX+UX1+2025_T3+type@chapter+block@ch1',
475+
0,
476+
);
477+
});
478+
456479
it('calls openDeleteModal when Delete is clicked in subsection menu', async () => {
457480
const user = userEvent.setup();
458481
await renderSubsectionMenu();

src/course-outline/outline-sidebar/info-sidebar/SubsectionInfoSidebar.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,12 @@ export const SubsectionSidebar = () => {
128128

129129
const handleBack = () => {
130130
if (selectedContainerState?.sectionId) {
131-
openContainerInfoSidebar(selectedContainerState.sectionId, undefined, selectedContainerState.sectionId);
131+
openContainerInfoSidebar(
132+
selectedContainerState.sectionId,
133+
undefined,
134+
selectedContainerState.sectionId,
135+
sectionIndex >= 0 ? sectionIndex : undefined,
136+
);
132137
return;
133138
}
134139
clearSelection();

0 commit comments

Comments
 (0)