Skip to content

Commit c4a439d

Browse files
authored
fix: show before and after title in diff preview (#2509)
Fix display of Before and After display name in section/subsection sync preview modal.
1 parent 8fe5fb6 commit c4a439d

7 files changed

Lines changed: 81 additions & 43 deletions

File tree

codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ coverage:
1111
ignore:
1212
- "src/grading-settings/grading-scale/react-ranger.js"
1313
- "src/generic/DraggableList/verticalSortableList.ts"
14+
- "src/container-comparison/data/api.mock.ts"
1415
- "src/index.js"
Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,79 @@
11
import userEvent from '@testing-library/user-event';
2-
import { mockGetContainerChildren, mockGetContainerMetadata } from '../library-authoring/data/api.mocks';
3-
import { initializeMocks, render, screen } from '../testUtils';
2+
import MockAdapter from 'axios-mock-adapter/types';
3+
import { getLibraryContainerApiUrl } from '@src/library-authoring/data/api';
4+
import { mockGetContainerChildren, mockGetContainerMetadata } from '@src/library-authoring/data/api.mocks';
5+
import { initializeMocks, render, screen } from '@src/testUtils';
46
import { CompareContainersWidget } from './CompareContainersWidget';
57
import { mockGetCourseContainerChildren } from './data/api.mock';
68

79
mockGetCourseContainerChildren.applyMock();
810
mockGetContainerChildren.applyMock();
11+
let axiosMock: MockAdapter;
912

1013
describe('CompareContainersWidget', () => {
1114
beforeEach(() => {
12-
initializeMocks();
15+
({ axiosMock } = initializeMocks());
1316
});
1417

1518
test('renders the component with a title', async () => {
19+
const url = getLibraryContainerApiUrl(mockGetContainerMetadata.sectionId);
20+
axiosMock.onGet(url).reply(200, { publishedDisplayName: 'Test Title' });
1621
render(<CompareContainersWidget
17-
title="Test Title"
1822
upstreamBlockId={mockGetContainerMetadata.sectionId}
1923
downstreamBlockId={mockGetCourseContainerChildren.sectionId}
2024
/>);
2125
expect((await screen.findAllByText('Test Title')).length).toEqual(2);
22-
expect((await screen.findAllByText('subsection block 0')).length).toEqual(2);
26+
expect((await screen.findAllByText('subsection block 0')).length).toEqual(1);
27+
expect((await screen.findAllByText('subsection block 00')).length).toEqual(1);
2328
expect((await screen.findAllByText('This subsection will be modified')).length).toEqual(3);
2429
expect((await screen.findAllByText('This subsection was modified')).length).toEqual(3);
25-
expect((await screen.findAllByText('subsection block 1')).length).toEqual(2);
26-
expect((await screen.findAllByText('subsection block 2')).length).toEqual(2);
30+
expect((await screen.findAllByText('subsection block 1')).length).toEqual(1);
31+
expect((await screen.findAllByText('subsection block 2')).length).toEqual(1);
32+
expect((await screen.findAllByText('subsection block 11')).length).toEqual(1);
33+
expect((await screen.findAllByText('subsection block 22')).length).toEqual(1);
2734
});
2835

2936
test('renders loading spinner when data is pending', async () => {
37+
const url = getLibraryContainerApiUrl(mockGetContainerMetadata.sectionIdLoading);
38+
axiosMock.onGet(url).reply(() => new Promise(() => {}));
3039
render(<CompareContainersWidget
31-
title="Test Title"
3240
upstreamBlockId={mockGetContainerMetadata.sectionIdLoading}
3341
downstreamBlockId={mockGetCourseContainerChildren.sectionIdLoading}
3442
/>);
35-
expect((await screen.findAllByText('Test Title')).length).toEqual(2);
3643
const spinner = await screen.findAllByRole('status');
37-
expect(spinner.length).toEqual(2);
44+
expect(spinner.length).toEqual(4);
3845
expect(spinner[0].textContent).toEqual('Loading...');
3946
expect(spinner[1].textContent).toEqual('Loading...');
47+
expect(spinner[2].textContent).toEqual('Loading...');
48+
expect(spinner[3].textContent).toEqual('Loading...');
4049
});
4150

4251
test('calls onRowClick when a row is clicked and updates diff view', async () => {
52+
// mocks title
53+
axiosMock.onGet(getLibraryContainerApiUrl(mockGetContainerMetadata.sectionId)).reply(200, { publishedDisplayName: 'Test Title' });
54+
axiosMock.onGet(
55+
getLibraryContainerApiUrl('lct:org1:Demo_course_generated:subsection:subsection-0'),
56+
).reply(200, { publishedDisplayName: 'subsection block 0' });
57+
4358
const user = userEvent.setup();
4459
render(<CompareContainersWidget
45-
title="Test Title"
4660
upstreamBlockId={mockGetContainerMetadata.sectionId}
4761
downstreamBlockId={mockGetCourseContainerChildren.sectionId}
4862
/>);
4963
expect((await screen.findAllByText('Test Title')).length).toEqual(2);
50-
let blocks = await screen.findAllByText('subsection block 0');
51-
expect(blocks.length).toEqual(2);
52-
await user.click(blocks[0]);
53-
// Breadcrumbs
54-
const breadcrumbs = await screen.findAllByRole('button', { name: 'subsection block 0' });
55-
expect(breadcrumbs.length).toEqual(2);
64+
// left i.e. before side block
65+
let block = await screen.findByText('subsection block 00');
66+
await user.click(block);
67+
// Breadcrumbs - shows old and new name
68+
expect(await screen.findByRole('button', { name: 'subsection block 00' })).toBeInTheDocument();
69+
expect(await screen.findByRole('button', { name: 'subsection block 0' })).toBeInTheDocument();
5670

5771
const removedRows = await screen.findAllByText('This unit was removed');
5872
// clicking on removed or added rows does not updated the page.
5973
await user.click(removedRows[0]);
6074
// Still in same page
61-
expect((await screen.findAllByRole('button', { name: 'subsection block 0' })).length).toEqual(2);
75+
expect(await screen.findByRole('button', { name: 'subsection block 00' })).toBeInTheDocument();
76+
expect(await screen.findByRole('button', { name: 'subsection block 0' })).toBeInTheDocument();
6277

6378
// Back breadcrumb
6479
const backbtns = await screen.findAllByRole('button', { name: 'Back' });
@@ -67,11 +82,12 @@ describe('CompareContainersWidget', () => {
6782
// Go back
6883
await user.click(backbtns[0]);
6984
expect((await screen.findAllByText('Test Title')).length).toEqual(2);
70-
blocks = await screen.findAllByText('subsection block 0');
71-
expect(blocks.length).toEqual(2);
85+
// right i.e. after side block
86+
block = await screen.findByText('subsection block 0');
7287

7388
// After side click also works
74-
await user.click(blocks[1]);
75-
expect((await screen.findAllByRole('button', { name: 'subsection block 0' })).length).toEqual(2);
89+
await user.click(block);
90+
expect(await screen.findByRole('button', { name: 'subsection block 00' })).toBeInTheDocument();
91+
expect(await screen.findByRole('button', { name: 'subsection block 0' })).toBeInTheDocument();
7692
});
7793
});

src/container-comparison/CompareContainersWidget.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import {
44
import { ArrowBack } from '@openedx/paragon/icons';
55
import { useCallback, useMemo, useState } from 'react';
66
import { ContainerType } from '@src/generic/key-utils';
7+
import ErrorAlert from '@src/generic/alert-error';
78
import { LoadingSpinner } from '@src/generic/Loading';
8-
import { useContainerChildren } from '@src/library-authoring/data/apiHooks';
9+
import { useContainer, useContainerChildren } from '@src/library-authoring/data/apiHooks';
910
import { useIntl } from '@edx/frontend-platform/i18n';
1011
import ChildrenPreview from './ChildrenPreview';
1112
import ContainerRow from './ContainerRow';
@@ -15,7 +16,6 @@ import { diffPreviewContainerChildren, isRowClickable } from './utils';
1516
import messages from './messages';
1617

1718
interface ContainerInfoProps {
18-
title: string;
1919
upstreamBlockId: string;
2020
downstreamBlockId: string;
2121
}
@@ -30,19 +30,24 @@ interface Props extends ContainerInfoProps {
3030
* Actual implementation of the displaying diff between children of containers.
3131
*/
3232
const CompareContainersWidgetInner = ({
33-
title,
3433
upstreamBlockId,
3534
downstreamBlockId,
3635
parent,
3736
onRowClick,
3837
onBackBtnClick,
3938
}: Props) => {
4039
const intl = useIntl();
41-
const { data, isPending } = useCourseContainerChildren(downstreamBlockId);
40+
const { data, isError, error } = useCourseContainerChildren(downstreamBlockId);
4241
const {
4342
data: libData,
44-
isPending: libPending,
43+
isError: isLibError,
44+
error: libError,
4545
} = useContainerChildren(upstreamBlockId, true);
46+
const {
47+
data: containerData,
48+
isError: isContainerTitleError,
49+
error: containerTitleError,
50+
} = useContainer(upstreamBlockId);
4651

4752
const result = useMemo(() => {
4853
if (!data || !libData) {
@@ -52,7 +57,7 @@ const CompareContainersWidgetInner = ({
5257
}, [data, libData]);
5358

5459
const renderBeforeChildren = useCallback(() => {
55-
if (isPending) {
60+
if (!result[0]) {
5661
return <div className="m-auto"><LoadingSpinner /></div>;
5762
}
5863

@@ -67,10 +72,10 @@ const CompareContainersWidgetInner = ({
6772
onClick={() => onRowClick(child)}
6873
/>
6974
));
70-
}, [isPending, result]);
75+
}, [result]);
7176

7277
const renderAfterChildren = useCallback(() => {
73-
if (libPending || isPending) {
78+
if (!result[1]) {
7479
return <div className="m-auto"><LoadingSpinner /></div>;
7580
}
7681

@@ -84,9 +89,13 @@ const CompareContainersWidgetInner = ({
8489
onClick={() => onRowClick(child)}
8590
/>
8691
));
87-
}, [libPending, isPending, result]);
92+
}, [result]);
93+
94+
const getTitleComponent = useCallback((title?: string | null) => {
95+
if (!title) {
96+
return <div className="m-auto"><LoadingSpinner /></div>;
97+
}
8898

89-
const getTitleComponent = () => {
9099
if (parent.length === 0) {
91100
return title;
92101
}
@@ -95,6 +104,7 @@ const CompareContainersWidgetInner = ({
95104
ariaLabel={intl.formatMessage(messages.breadcrumbAriaLabel)}
96105
links={[
97106
{
107+
// This raises failed prop-type error as label expects a string but it works without any issues
98108
label: <Stack direction="horizontal" gap={1}><Icon size="xs" src={ArrowBack} />Back</Stack>,
99109
onClick: onBackBtnClick,
100110
variant: 'link',
@@ -110,20 +120,24 @@ const CompareContainersWidgetInner = ({
110120
linkAs={Button}
111121
/>
112122
);
113-
};
123+
}, [parent]);
124+
125+
if (isError || isLibError || isContainerTitleError) {
126+
return <ErrorAlert error={error || libError || containerTitleError} />;
127+
}
114128

115129
return (
116130
<div className="row">
117131
<div className="col col-6 p-1">
118132
<Card className="p-4">
119-
<ChildrenPreview title={getTitleComponent()} side="Before">
133+
<ChildrenPreview title={getTitleComponent(data?.displayName)} side="Before">
120134
{renderBeforeChildren()}
121135
</ChildrenPreview>
122136
</Card>
123137
</div>
124138
<div className="col col-6 p-1">
125139
<Card className="p-4">
126-
<ChildrenPreview title={getTitleComponent()} side="After">
140+
<ChildrenPreview title={getTitleComponent(containerData?.publishedDisplayName)} side="After">
127141
{renderAfterChildren()}
128142
</ChildrenPreview>
129143
</Card>
@@ -137,11 +151,10 @@ const CompareContainersWidgetInner = ({
137151
* and allows the user to select the container to view. This is a wrapper component that maintains current
138152
* source state. Actual implementation of the diff view is done by CompareContainersWidgetInner.
139153
*/
140-
export const CompareContainersWidget = ({ title, upstreamBlockId, downstreamBlockId }: ContainerInfoProps) => {
154+
export const CompareContainersWidget = ({ upstreamBlockId, downstreamBlockId }: ContainerInfoProps) => {
141155
const [currentContainerState, setCurrentContainerState] = useState<ContainerInfoProps & {
142156
parent: ContainerInfoProps[];
143157
}>({
144-
title,
145158
upstreamBlockId,
146159
downstreamBlockId,
147160
parent: [],
@@ -153,11 +166,9 @@ export const CompareContainersWidget = ({ title, upstreamBlockId, downstreamBloc
153166
}
154167

155168
setCurrentContainerState((prev) => ({
156-
title: row.name,
157169
upstreamBlockId: row.id!,
158170
downstreamBlockId: row.downstreamId!,
159171
parent: [...prev.parent, {
160-
title: prev.title,
161172
upstreamBlockId: prev.upstreamBlockId,
162173
downstreamBlockId: prev.downstreamBlockId,
163174
}],
@@ -172,7 +183,6 @@ export const CompareContainersWidget = ({ title, upstreamBlockId, downstreamBloc
172183
}
173184
const prevParent = prev.parent[prev.parent.length - 1];
174185
return {
175-
title: prevParent!.title,
176186
upstreamBlockId: prevParent!.upstreamBlockId,
177187
downstreamBlockId: prevParent!.downstreamBlockId,
178188
parent: prev.parent.slice(0, -1),
@@ -182,7 +192,6 @@ export const CompareContainersWidget = ({ title, upstreamBlockId, downstreamBloc
182192

183193
return (
184194
<CompareContainersWidgetInner
185-
title={currentContainerState.title}
186195
upstreamBlockId={currentContainerState.upstreamBlockId}
187196
downstreamBlockId={currentContainerState.downstreamBlockId}
188197
parent={currentContainerState.parent}

src/container-comparison/data/api.mock.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* istanbul ignore file */
12
import { CourseContainerChildrenData } from '@src/course-unit/data/types';
23
import * as unitApi from '@src/course-unit/data/api';
34

@@ -9,30 +10,35 @@ import * as unitApi from '@src/course-unit/data/api';
910
export async function mockGetCourseContainerChildren(containerId: string): Promise<CourseContainerChildrenData> {
1011
const numChildren: number = 3;
1112
let blockType: string;
13+
let displayName: string;
1214
switch (containerId) {
1315
case mockGetCourseContainerChildren.unitId:
1416
blockType = 'text';
17+
displayName = 'unit block 00';
1518
break;
1619
case mockGetCourseContainerChildren.sectionId:
1720
blockType = 'subsection';
21+
displayName = 'Test Title';
1822
break;
1923
case mockGetCourseContainerChildren.subsectionId:
2024
blockType = 'unit';
25+
displayName = 'subsection block 00';
2126
break;
2227
case mockGetCourseContainerChildren.unitIdLoading:
2328
case mockGetCourseContainerChildren.sectionIdLoading:
2429
case mockGetCourseContainerChildren.subsectionIdLoading:
2530
return new Promise(() => { });
2631
default:
2732
blockType = 'unit';
33+
displayName = 'subsection block 00';
2834
break;
2935
}
3036
const children = Array(numChildren).fill(mockGetCourseContainerChildren.childTemplate).map((child, idx) => (
3137
{
3238
...child,
3339
// Generate a unique ID for each child block to avoid "duplicate key" errors in tests
3440
id: `block-v1:UNIX+UX1+2025_T3+type@${blockType}+block@${idx}`,
35-
name: `${blockType} block ${idx}`,
41+
name: `${blockType} block ${idx}${idx}`,
3642
blockType,
3743
upstreamLink: {
3844
upstreamRef: `lct:org1:Demo_course_generated:${blockType}:${blockType}-${idx}`,
@@ -47,6 +53,7 @@ export async function mockGetCourseContainerChildren(containerId: string): Promi
4753
canPasteComponent: true,
4854
isPublished: false,
4955
children,
56+
displayName,
5057
});
5158
}
5259
mockGetCourseContainerChildren.unitId = 'block-v1:UNIX+UX1+2025_T3+type@unit+block@0';

src/container-comparison/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { defineMessages } from '@edx/frontend-platform/i18n';
22

33
const messages = defineMessages({
4+
error: {
5+
id: 'course-authoring.container-comparison.diff.error.message',
6+
defaultMessage: 'Unexpected error: Failed to fetch container data',
7+
description: 'Generic error message',
8+
},
49
removedDiffBeforeMessage: {
510
id: 'course-authoring.container-comparison.diff.before.removed-message',
611
defaultMessage: 'This {blockType} will be removed in the new version',

src/course-unit/data/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ export interface CourseContainerChildrenData {
4242
canPasteComponent: boolean;
4343
children: ContainerChildData[],
4444
isPublished: boolean;
45+
displayName: string;
4546
}

src/course-unit/preview-changes/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ export const PreviewLibraryXBlockChanges = ({
141141
if (blockData.isContainer) {
142142
return (
143143
<CompareContainersWidget
144-
title={blockData.displayName}
145144
upstreamBlockId={blockData.upstreamBlockId}
146145
downstreamBlockId={blockData.downstreamBlockId}
147146
/>

0 commit comments

Comments
 (0)