Skip to content

Commit baa42f9

Browse files
committed
test: Adding tests for menus in all sidebars
1 parent a475f59 commit baa42f9

4 files changed

Lines changed: 562 additions & 3 deletions

File tree

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

Lines changed: 313 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { initializeMocks, render, screen } from '@src/testUtils';
1+
import { fireEvent, initializeMocks, render, screen } from '@src/testUtils';
22
import { SelectionState } from '@src/data/types';
33
import { OutlineSidebarProvider } from '@src/course-outline/outline-sidebar/OutlineSidebarContext';
44
import { getXBlockApiUrl } from '@src/course-outline/data/api';
55
import userEvent from '@testing-library/user-event';
6+
import { getDownstreamApiUrl } from '@src/generic/unlink-modal/data/api';
67
import { InfoSidebar } from './InfoSidebar';
78

89
let selectedContainerState: SelectionState | undefined;
@@ -11,6 +12,8 @@ jest.mock('@src/course-outline/outline-sidebar/OutlineSidebarContext', () => ({
1112
useOutlineSidebarContext: () => ({
1213
...jest.requireActual('@src/course-outline/outline-sidebar/OutlineSidebarContext').useOutlineSidebarContext(),
1314
selectedContainerState,
15+
clearSelection: jest.fn(),
16+
setSelectedContainerState: jest.fn(),
1417
}),
1518
}));
1619

@@ -23,12 +26,33 @@ jest.mock('@src/course-outline/data/apiHooks', () => ({
2326
}));
2427

2528
const openPublishModal = jest.fn();
29+
const openDeleteModal = jest.fn();
30+
const openUnlinkModal = jest.fn();
31+
const handleDuplicateSectionSubmit = jest.fn();
32+
const handleDuplicateUnitSubmit = jest.fn();
33+
const handleDuplicateSubsectionSubmit = jest.fn();
34+
const mockedNavigate = jest.fn();
35+
36+
jest.mock('react-router-dom', () => ({
37+
...jest.requireActual('react-router-dom'),
38+
useNavigate: () => mockedNavigate,
39+
}));
40+
2641
jest.mock('@src/CourseAuthoringContext', () => ({
2742
useCourseAuthoringContext: () => ({
2843
courseId: 5,
2944
setCurrentSelection: jest.fn(),
3045
openPublishModal,
46+
openDeleteModal,
47+
openUnlinkModal,
48+
handleDuplicateUnitSubmit,
3149
getUnitUrl: jest.fn(),
50+
sections: [],
51+
updateUnitOrderByIndex: jest.fn(),
52+
handleDuplicateSectionSubmit,
53+
updateSectionOrderByIndex: jest.fn(),
54+
handleDuplicateSubsectionSubmit,
55+
updateSubsectionOrderByIndex: jest.fn(),
3256
}),
3357
}));
3458

@@ -43,6 +67,12 @@ describe('InfoSidebar component', () => {
4367
beforeEach(() => {
4468
const mocks = initializeMocks();
4569
axiosMock = mocks.axiosMock;
70+
openDeleteModal.mockClear();
71+
openUnlinkModal.mockClear();
72+
handleDuplicateSectionSubmit.mockClear();
73+
handleDuplicateUnitSubmit.mockClear();
74+
handleDuplicateSubsectionSubmit.mockClear();
75+
mockedNavigate.mockClear();
4676
});
4777

4878
it('renders InfoSidebar with course info if selectedContainerState is undefined', async () => {
@@ -133,4 +163,285 @@ describe('InfoSidebar component', () => {
133163
sectionId: selectedContainerState.sectionId,
134164
});
135165
});
136-
});
166+
167+
describe('UnitSidebar menus', () => {
168+
const unitId = 'block-v1:UNIX+UX1+2025_T3+type@vertical+block@unit1';
169+
const upstreamRef = 'lb:org:lib:vertical:unit-id';
170+
171+
const unitData = {
172+
id: unitId,
173+
displayName: 'unit name',
174+
category: 'vertical',
175+
hasChanges: false,
176+
actions: { deletable: true, duplicable: true, draggable: false },
177+
upstreamInfo: null,
178+
};
179+
180+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
181+
const renderUnitMenu = async (data: any = unitData) => {
182+
selectedContainerState = {
183+
currentId: unitId,
184+
subsectionId: 'block-v1:UNIX+UX1+2025_T3+type@sequential+block@seq1',
185+
sectionId: 'block-v1:UNIX+UX1+2025_T3+type@chapter+block@ch1',
186+
};
187+
axiosMock.onGet(getXBlockApiUrl(unitId)).reply(200, data);
188+
renderComponent();
189+
await screen.findByText(data.displayName);
190+
await screen.findByRole('button', { name: 'Item Menu' });
191+
};
192+
193+
it('calls openDeleteModal when Delete is clicked in unit menu', async () => {
194+
const user = userEvent.setup();
195+
await renderUnitMenu();
196+
197+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
198+
fireEvent.click(menuToggle);
199+
200+
const deleteBtn = await screen.findByText('Delete');
201+
await user.click(deleteBtn);
202+
203+
expect(openDeleteModal).toHaveBeenCalled();
204+
});
205+
206+
it('calls handleDuplicateUnitSubmit when Duplicate is clicked in unit menu', async () => {
207+
const user = userEvent.setup();
208+
await renderUnitMenu();
209+
210+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
211+
fireEvent.click(menuToggle);
212+
213+
const duplicateBtn = await screen.findByText('Duplicate');
214+
await user.click(duplicateBtn);
215+
216+
expect(handleDuplicateUnitSubmit).toHaveBeenCalled();
217+
});
218+
219+
it('calls openUnlinkModal when Unlink is clicked in unit menu', async () => {
220+
const user = userEvent.setup();
221+
const unitWithUpstream = {
222+
...unitData,
223+
actions: { ...unitData.actions, unlinkable: true },
224+
upstreamInfo: { upstreamRef },
225+
};
226+
await renderUnitMenu(unitWithUpstream);
227+
228+
axiosMock.onDelete(getDownstreamApiUrl(unitId)).reply(200, {});
229+
230+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
231+
fireEvent.click(menuToggle);
232+
233+
const unlinkBtn = await screen.findByText('Unlink from Library');
234+
await user.click(unlinkBtn);
235+
236+
expect(openUnlinkModal).toHaveBeenCalledWith(expect.objectContaining({
237+
value: unitWithUpstream,
238+
sectionId: selectedContainerState?.sectionId,
239+
subsectionId: selectedContainerState?.subsectionId,
240+
}));
241+
});
242+
243+
it('navigates to library when View in Library is clicked in unit menu', async () => {
244+
const user = userEvent.setup();
245+
const unitWithUpstream = {
246+
...unitData,
247+
upstreamInfo: { upstreamRef },
248+
};
249+
await renderUnitMenu(unitWithUpstream);
250+
251+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
252+
fireEvent.click(menuToggle);
253+
254+
const viewLibBtn = await screen.findByText('View in Library');
255+
await user.click(viewLibBtn);
256+
257+
expect(mockedNavigate).toHaveBeenCalledWith(
258+
expect.stringContaining('/library/'),
259+
);
260+
});
261+
});
262+
263+
describe('SubsectionSidebar menus', () => {
264+
const subsectionId = 'block-v1:UNIX+UX1+2025_T3+type@sequential+block@sub1';
265+
const upstreamRef = 'lb:org:lib:sequential:sub-id';
266+
267+
const subsectionData = {
268+
id: subsectionId,
269+
displayName: 'subsection name',
270+
category: 'sequential',
271+
hasChanges: false,
272+
actions: { deletable: true, duplicable: true, draggable: false },
273+
upstreamInfo: null,
274+
};
275+
276+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
277+
const renderSubsectionMenu = async (data: any = subsectionData) => {
278+
selectedContainerState = {
279+
currentId: subsectionId,
280+
subsectionId,
281+
sectionId: 'block-v1:UNIX+UX1+2025_T3+type@chapter+block@ch1',
282+
};
283+
axiosMock.onGet(getXBlockApiUrl(subsectionId)).reply(200, data);
284+
renderComponent();
285+
await screen.findByText(data.displayName);
286+
await screen.findByRole('button', { name: 'Item Menu' });
287+
};
288+
289+
it('calls openDeleteModal when Delete is clicked in subsection menu', async () => {
290+
const user = userEvent.setup();
291+
await renderSubsectionMenu();
292+
293+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
294+
fireEvent.click(menuToggle);
295+
296+
const deleteBtn = await screen.findByText('Delete');
297+
await user.click(deleteBtn);
298+
299+
expect(openDeleteModal).toHaveBeenCalled();
300+
});
301+
302+
it('calls handleDuplicateSubsectionSubmit when Duplicate is clicked in subsection menu', async () => {
303+
const user = userEvent.setup();
304+
await renderSubsectionMenu();
305+
306+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
307+
fireEvent.click(menuToggle);
308+
309+
const duplicateBtn = await screen.findByText('Duplicate');
310+
await user.click(duplicateBtn);
311+
312+
expect(handleDuplicateSubsectionSubmit).toHaveBeenCalled();
313+
});
314+
315+
it('calls openUnlinkModal when Unlink is clicked in subsection menu', async () => {
316+
const user = userEvent.setup();
317+
const subsectionWithUpstream = {
318+
...subsectionData,
319+
actions: { ...subsectionData.actions, unlinkable: true },
320+
upstreamInfo: { upstreamRef },
321+
};
322+
await renderSubsectionMenu(subsectionWithUpstream);
323+
324+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
325+
fireEvent.click(menuToggle);
326+
327+
const unlinkBtn = await screen.findByText('Unlink from Library');
328+
await user.click(unlinkBtn);
329+
330+
expect(openUnlinkModal).toHaveBeenCalledWith(expect.objectContaining({
331+
value: subsectionWithUpstream,
332+
sectionId: selectedContainerState?.sectionId,
333+
}));
334+
});
335+
336+
it('navigates to library when View in Library is clicked in subsection menu', async () => {
337+
const user = userEvent.setup();
338+
const subsectionWithUpstream = {
339+
...subsectionData,
340+
upstreamInfo: { upstreamRef },
341+
};
342+
await renderSubsectionMenu(subsectionWithUpstream);
343+
344+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
345+
fireEvent.click(menuToggle);
346+
347+
const viewLibBtn = await screen.findByText('View in Library');
348+
await user.click(viewLibBtn);
349+
350+
expect(mockedNavigate).toHaveBeenCalledWith(
351+
expect.stringContaining('/library/'),
352+
);
353+
});
354+
});
355+
356+
describe('SectionSidebar menus', () => {
357+
const sectionId = 'block-v1:UNIX+UX1+2025_T3+type@chapter+block@sec1';
358+
const upstreamRef = 'lb:org:lib:chapter:sec-id';
359+
360+
const sectionData = {
361+
id: sectionId,
362+
displayName: 'section name',
363+
category: 'chapter',
364+
hasChanges: false,
365+
actions: { deletable: true, duplicable: true, draggable: false },
366+
upstreamInfo: null,
367+
};
368+
369+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
370+
const renderSectionMenu = async (data: any = sectionData) => {
371+
selectedContainerState = {
372+
currentId: sectionId,
373+
sectionId,
374+
};
375+
axiosMock.onGet(getXBlockApiUrl(sectionId)).reply(200, data);
376+
renderComponent();
377+
await screen.findByText(data.displayName);
378+
await screen.findByRole('button', { name: 'Item Menu' });
379+
};
380+
381+
it('calls openDeleteModal when Delete is clicked in section menu', async () => {
382+
const user = userEvent.setup();
383+
await renderSectionMenu();
384+
385+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
386+
fireEvent.click(menuToggle);
387+
388+
const deleteBtn = await screen.findByText('Delete');
389+
await user.click(deleteBtn);
390+
391+
expect(openDeleteModal).toHaveBeenCalled();
392+
});
393+
394+
it('calls handleDuplicateSectionSubmit when Duplicate is clicked in section menu', async () => {
395+
const user = userEvent.setup();
396+
await renderSectionMenu();
397+
398+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
399+
fireEvent.click(menuToggle);
400+
401+
const duplicateBtn = await screen.findByText('Duplicate');
402+
await user.click(duplicateBtn);
403+
404+
expect(handleDuplicateSectionSubmit).toHaveBeenCalled();
405+
});
406+
407+
it('calls openUnlinkModal when Unlink is clicked in section menu', async () => {
408+
const user = userEvent.setup();
409+
const sectionWithUpstream = {
410+
...sectionData,
411+
actions: { ...sectionData.actions, unlinkable: true },
412+
upstreamInfo: { upstreamRef },
413+
};
414+
await renderSectionMenu(sectionWithUpstream);
415+
416+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
417+
fireEvent.click(menuToggle);
418+
419+
const unlinkBtn = await screen.findByText('Unlink from Library');
420+
await user.click(unlinkBtn);
421+
422+
expect(openUnlinkModal).toHaveBeenCalledWith(expect.objectContaining({
423+
value: sectionWithUpstream,
424+
sectionId,
425+
}));
426+
});
427+
428+
it('navigates to library when View in Library is clicked in section menu', async () => {
429+
const user = userEvent.setup();
430+
const sectionWithUpstream = {
431+
...sectionData,
432+
upstreamInfo: { upstreamRef },
433+
};
434+
await renderSectionMenu(sectionWithUpstream);
435+
436+
const menuToggle = screen.getByRole('button', { name: 'Item Menu' });
437+
fireEvent.click(menuToggle);
438+
439+
const viewLibBtn = await screen.findByText('View in Library');
440+
await user.click(viewLibBtn);
441+
442+
expect(mockedNavigate).toHaveBeenCalledWith(
443+
expect.stringContaining('/library/'),
444+
);
445+
});
446+
});
447+
});

0 commit comments

Comments
 (0)