Skip to content

Commit 25830a2

Browse files
authored
feat: Show sections/subsections/units available for sync in library sync page [FC-0097] (#2271)
- Adds Units, Subsection, and section cards in the libraries sync page. - Rename of `mockGetEntityLinks` to `mockGetComponentEntityLinks` - Use the top-level parent logic - Which user roles will this change impact? "Course Author".
1 parent 6ce7b86 commit 25830a2

21 files changed

+471
-259
lines changed

src/course-libraries/CourseLibraries.test.tsx

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -78,29 +78,29 @@ describe('<CourseLibraries />', () => {
7878
const user = userEvent.setup();
7979
await renderCourseLibrariesPage(mockGetEntityLinks.courseKey);
8080
const allTab = await screen.findByRole('tab', { name: 'Libraries' });
81-
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 5' });
81+
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 7' });
8282
// review tab should be open by default as outOfSyncCount is greater than 0
8383
expect(reviewTab).toHaveAttribute('aria-selected', 'true');
8484

8585
await user.click(allTab);
8686
const alert = await screen.findByRole('alert');
8787
expect(await within(alert).findByText(
88-
'5 library components are out of sync. Review updates to accept or ignore changes',
88+
'7 library components are out of sync. Review updates to accept or ignore changes',
8989
)).toBeInTheDocument();
9090
expect(allTab).toHaveAttribute('aria-selected', 'true');
9191

9292
const reviewBtn = await screen.findByRole('button', { name: 'Review' });
9393
await user.click(reviewBtn);
9494

9595
expect(allTab).toHaveAttribute('aria-selected', 'false');
96-
expect(await screen.findByRole('tab', { name: 'Review Content Updates 5' })).toHaveAttribute('aria-selected', 'true');
96+
expect(await screen.findByRole('tab', { name: 'Review Content Updates 7' })).toHaveAttribute('aria-selected', 'true');
9797
expect(alert).not.toBeInTheDocument();
9898
});
9999

100100
it('hide alert on dismiss', async () => {
101101
const user = userEvent.setup();
102102
await renderCourseLibrariesPage(mockGetEntityLinks.courseKey);
103-
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 5' });
103+
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 7' });
104104
// review tab should be open by default as outOfSyncCount is greater than 0
105105
expect(reviewTab).toHaveAttribute('aria-selected', 'true');
106106
const allTab = await screen.findByRole('tab', { name: 'Libraries' });
@@ -109,7 +109,7 @@ describe('<CourseLibraries />', () => {
109109

110110
const alert = await screen.findByRole('alert');
111111
expect(await within(alert).findByText(
112-
'5 library components are out of sync. Review updates to accept or ignore changes',
112+
'7 library components are out of sync. Review updates to accept or ignore changes',
113113
)).toBeInTheDocument();
114114
const dismissBtn = await screen.findByRole('button', { name: 'Dismiss' });
115115
await user.click(dismissBtn);
@@ -118,7 +118,7 @@ describe('<CourseLibraries />', () => {
118118
// review updates button
119119
const reviewActionBtn = await screen.findByRole('button', { name: 'Review Updates' });
120120
await user.click(reviewActionBtn);
121-
expect(await screen.findByRole('tab', { name: 'Review Content Updates 5' })).toHaveAttribute('aria-selected', 'true');
121+
expect(await screen.findByRole('tab', { name: 'Review Content Updates 7' })).toHaveAttribute('aria-selected', 'true');
122122
});
123123

124124
it('show alert if max lastPublishedDate is greated than the local storage value', async () => {
@@ -131,14 +131,14 @@ describe('<CourseLibraries />', () => {
131131

132132
await renderCourseLibrariesPage(mockGetEntityLinks.courseKey);
133133
const allTab = await screen.findByRole('tab', { name: 'Libraries' });
134-
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 5' });
134+
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 7' });
135135
// review tab should be open by default as outOfSyncCount is greater than 0
136136
expect(reviewTab).toHaveAttribute('aria-selected', 'true');
137137

138138
await user.click(allTab);
139139
const alert = await screen.findByRole('alert');
140140
expect(await within(alert).findByText(
141-
'5 library components are out of sync. Review updates to accept or ignore changes',
141+
'7 library components are out of sync. Review updates to accept or ignore changes',
142142
)).toBeInTheDocument();
143143
});
144144

@@ -152,14 +152,12 @@ describe('<CourseLibraries />', () => {
152152

153153
await renderCourseLibrariesPage(mockGetEntityLinks.courseKey);
154154
const allTab = await screen.findByRole('tab', { name: 'Libraries' });
155-
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 5' });
155+
const reviewTab = await screen.findByRole('tab', { name: 'Review Content Updates 7' });
156156
// review tab should be open by default as outOfSyncCount is greater than 0
157157
expect(reviewTab).toHaveAttribute('aria-selected', 'true');
158158
await user.click(allTab);
159159
expect(allTab).toHaveAttribute('aria-selected', 'true');
160160

161-
screen.logTestingPlaygroundURL();
162-
163161
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
164162
});
165163
});
@@ -195,60 +193,93 @@ describe('<CourseLibraries ReviewTab />', () => {
195193
});
196194

197195
it('shows all readyToSync links', async () => {
198-
await renderCourseLibrariesReviewPage(mockGetEntityLinksSummaryByDownstreamContext.courseKey);
196+
await renderCourseLibrariesReviewPage();
199197
const updateBtns = await screen.findAllByRole('button', { name: 'Update' });
200-
expect(updateBtns.length).toEqual(5);
198+
expect(updateBtns.length).toEqual(7);
201199
const ignoreBtns = await screen.findAllByRole('button', { name: 'Ignore' });
202-
expect(ignoreBtns.length).toEqual(5);
200+
expect(ignoreBtns.length).toEqual(7);
203201
});
204202

205-
it('update changes works', async () => {
203+
test.each([
204+
{
205+
label: 'update changes works with components',
206+
itemIndex: 0,
207+
expectedToastMsg: 'Success! "Dropdown" is updated',
208+
},
209+
{
210+
label: 'update changes works with containers',
211+
itemIndex: 5,
212+
expectedToastMsg: 'Success! "Unit 1" is updated',
213+
},
214+
])('$label', async ({ itemIndex, expectedToastMsg }) => {
206215
const user = userEvent.setup();
207216
const mockInvalidateQueries = jest.spyOn(queryClient, 'invalidateQueries');
208-
const usageKey = mockGetEntityLinks.response[0].downstreamUsageKey;
217+
const usageKey = mockGetEntityLinks.response[itemIndex].downstreamUsageKey;
209218
axiosMock.onPost(libraryBlockChangesUrl(usageKey)).reply(200, {});
210219
await renderCourseLibrariesReviewPage(mockGetEntityLinksSummaryByDownstreamContext.courseKey);
211220
const updateBtns = await screen.findAllByRole('button', { name: 'Update' });
212-
expect(updateBtns.length).toEqual(5);
213-
await user.click(updateBtns[0]);
221+
expect(updateBtns.length).toEqual(7);
222+
await user.click(updateBtns[itemIndex]);
214223
await waitFor(() => {
215224
expect(axiosMock.history.post.length).toEqual(1);
216225
});
217226
expect(axiosMock.history.post[0].url).toEqual(libraryBlockChangesUrl(usageKey));
218-
expect(mockShowToast).toHaveBeenCalledWith('Success! "Dropdown" is updated');
219-
expect(mockInvalidateQueries).toHaveBeenCalledWith(['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX']);
227+
expect(mockShowToast).toHaveBeenCalledWith(expectedToastMsg);
228+
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX'] });
220229
});
221230

222-
it('update changes works in preview modal', async () => {
231+
test.each([
232+
{
233+
label: 'update changes works in preview modal with components',
234+
itemIndex: 0,
235+
expectedToastMsg: 'Success! "Dropdown" is updated',
236+
},
237+
{
238+
label: 'update changes works in preview modal with containers',
239+
itemIndex: 5,
240+
expectedToastMsg: 'Success! "Unit 1" is updated',
241+
},
242+
])('$label', async ({ itemIndex, expectedToastMsg }) => {
223243
const user = userEvent.setup();
224244
const mockInvalidateQueries = jest.spyOn(queryClient, 'invalidateQueries');
225-
const usageKey = mockGetEntityLinks.response[0].downstreamUsageKey;
245+
const usageKey = mockGetEntityLinks.response[itemIndex].downstreamUsageKey;
226246
axiosMock.onPost(libraryBlockChangesUrl(usageKey)).reply(200, {});
227247
await renderCourseLibrariesReviewPage(mockGetEntityLinksSummaryByDownstreamContext.courseKey);
228248
const previewBtns = await screen.findAllByRole('button', { name: 'Review Updates' });
229-
expect(previewBtns.length).toEqual(5);
230-
await user.click(previewBtns[0]);
249+
expect(previewBtns.length).toEqual(7);
250+
await user.click(previewBtns[itemIndex]);
231251
const dialog = await screen.findByRole('dialog');
232252
const confirmBtn = await within(dialog).findByRole('button', { name: 'Accept changes' });
233253
await user.click(confirmBtn);
234254
await waitFor(() => {
235255
expect(axiosMock.history.post.length).toEqual(1);
236256
});
237257
expect(axiosMock.history.post[0].url).toEqual(libraryBlockChangesUrl(usageKey));
238-
expect(mockShowToast).toHaveBeenCalledWith('Success! "Dropdown" is updated');
239-
expect(mockInvalidateQueries).toHaveBeenCalledWith(['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX']);
258+
expect(mockShowToast).toHaveBeenCalledWith(expectedToastMsg);
259+
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX'] });
240260
});
241261

242-
it('ignore change works', async () => {
262+
test.each([
263+
{
264+
label: 'ignore change works with components',
265+
itemIndex: 0,
266+
expectedToastMsg: '"Dropdown" will remain out of sync with library content. You will be notified when this component is updated again.',
267+
},
268+
{
269+
label: 'ignore change works with containers',
270+
itemIndex: 5,
271+
expectedToastMsg: '"Unit 1" will remain out of sync with library content. You will be notified when this component is updated again.',
272+
},
273+
])('$label', async ({ itemIndex, expectedToastMsg }) => {
243274
const user = userEvent.setup();
244275
const mockInvalidateQueries = jest.spyOn(queryClient, 'invalidateQueries');
245-
const usageKey = mockGetEntityLinks.response[0].downstreamUsageKey;
276+
const usageKey = mockGetEntityLinks.response[itemIndex].downstreamUsageKey;
246277
axiosMock.onDelete(libraryBlockChangesUrl(usageKey)).reply(204, {});
247278
await renderCourseLibrariesReviewPage(mockGetEntityLinksSummaryByDownstreamContext.courseKey);
248279
const ignoreBtns = await screen.findAllByRole('button', { name: 'Ignore' });
249-
expect(ignoreBtns.length).toEqual(5);
280+
expect(ignoreBtns.length).toEqual(7);
250281
// Show confirmation modal on clicking ignore.
251-
await user.click(ignoreBtns[0]);
282+
await user.click(ignoreBtns[itemIndex]);
252283
const dialog = await screen.findByRole('dialog', { name: 'Ignore these changes?' });
253284
expect(dialog).toBeInTheDocument();
254285
const confirmBtn = await within(dialog).findByRole('button', { name: 'Ignore' });
@@ -257,21 +288,30 @@ describe('<CourseLibraries ReviewTab />', () => {
257288
expect(axiosMock.history.delete.length).toEqual(1);
258289
});
259290
expect(axiosMock.history.delete[0].url).toEqual(libraryBlockChangesUrl(usageKey));
260-
expect(mockShowToast).toHaveBeenCalledWith(
261-
'"Dropdown" will remain out of sync with library content. You will be notified when this component is updated again.',
262-
);
263-
expect(mockInvalidateQueries).toHaveBeenCalledWith(['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX']);
291+
expect(mockShowToast).toHaveBeenCalledWith(expectedToastMsg);
292+
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX'] });
264293
});
265294

266-
it('ignore change works in preview', async () => {
295+
test.each([
296+
{
297+
label: 'ignore change works with components',
298+
itemIndex: 0,
299+
expectedToastMsg: '"Dropdown" will remain out of sync with library content. You will be notified when this component is updated again.',
300+
},
301+
{
302+
label: 'ignore change works with containers',
303+
itemIndex: 5,
304+
expectedToastMsg: '"Unit 1" will remain out of sync with library content. You will be notified when this component is updated again.',
305+
},
306+
])('$label', async ({ itemIndex, expectedToastMsg }) => {
267307
const user = userEvent.setup();
268308
const mockInvalidateQueries = jest.spyOn(queryClient, 'invalidateQueries');
269-
const usageKey = mockGetEntityLinks.response[0].downstreamUsageKey;
309+
const usageKey = mockGetEntityLinks.response[itemIndex].downstreamUsageKey;
270310
axiosMock.onDelete(libraryBlockChangesUrl(usageKey)).reply(204, {});
271311
await renderCourseLibrariesReviewPage(mockGetEntityLinksSummaryByDownstreamContext.courseKey);
272312
const previewBtns = await screen.findAllByRole('button', { name: 'Review Updates' });
273-
expect(previewBtns.length).toEqual(5);
274-
await user.click(previewBtns[0]);
313+
expect(previewBtns.length).toEqual(7);
314+
await user.click(previewBtns[itemIndex]);
275315
const previewDialog = await screen.findByRole('dialog');
276316
const ignoreBtn = await within(previewDialog).findByRole('button', { name: 'Ignore changes' });
277317
await user.click(ignoreBtn);
@@ -284,9 +324,7 @@ describe('<CourseLibraries ReviewTab />', () => {
284324
expect(axiosMock.history.delete.length).toEqual(1);
285325
});
286326
expect(axiosMock.history.delete[0].url).toEqual(libraryBlockChangesUrl(usageKey));
287-
expect(mockShowToast).toHaveBeenCalledWith(
288-
'"Dropdown" will remain out of sync with library content. You will be notified when this component is updated again.',
289-
);
290-
expect(mockInvalidateQueries).toHaveBeenCalledWith(['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX']);
327+
expect(mockShowToast).toHaveBeenCalledWith(expectedToastMsg);
328+
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['courseLibraries', 'course-v1:OpenEdx+DemoX+CourseX'] });
291329
});
292330
});

0 commit comments

Comments
 (0)