-
-
+
+
+ >
);
};
@@ -225,6 +261,11 @@ UnitCard.propTypes = {
isHeaderVisible: PropTypes.bool,
enableCopyPasteUnits: PropTypes.bool,
discussionEnabled: PropTypes.bool,
+ upstreamInfo: PropTypes.shape({
+ readyToSync: PropTypes.bool.isRequired,
+ upstreamRef: PropTypes.string.isRequired,
+ versionSynced: PropTypes.number.isRequired,
+ }).isRequired,
}).isRequired,
subsection: PropTypes.shape({
id: PropTypes.string.isRequired,
diff --git a/src/course-outline/unit-card/UnitCard.test.jsx b/src/course-outline/unit-card/UnitCard.test.jsx
index 91e7207d96..663fa59c28 100644
--- a/src/course-outline/unit-card/UnitCard.test.jsx
+++ b/src/course-outline/unit-card/UnitCard.test.jsx
@@ -1,5 +1,6 @@
import {
- act, render, fireEvent, within,
+ act, render, fireEvent, within, screen,
+ waitFor,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
@@ -11,6 +12,17 @@ import UnitCard from './UnitCard';
import cardMessages from '../card-header/messages';
let store;
+const mockUseAcceptLibraryBlockChanges = jest.fn();
+const mockUseIgnoreLibraryBlockChanges = jest.fn();
+
+jest.mock('../../course-unit/data/apiHooks', () => ({
+ useAcceptLibraryBlockChanges: () => ({
+ mutateAsync: mockUseAcceptLibraryBlockChanges,
+ }),
+ useIgnoreLibraryBlockChanges: () => ({
+ mutateAsync: mockUseIgnoreLibraryBlockChanges,
+ }),
+}));
const section = {
id: '1',
@@ -43,6 +55,11 @@ const unit = {
duplicable: true,
},
isHeaderVisible: true,
+ upstreamInfo: {
+ readyToSync: true,
+ upstreamRef: 'lct:org1:lib1:unit:1',
+ versionSynced: 1,
+ },
};
const queryClient = new QueryClient();
@@ -147,4 +164,51 @@ describe('', () => {
});
expect(queryByRole('status')).not.toBeInTheDocument();
});
+
+ it('should sync unit changes from upstream', async () => {
+ renderComponent();
+
+ expect(await screen.findByTestId('unit-card-header')).toBeInTheDocument();
+
+ // Click on sync button
+ const syncButton = screen.getByRole('button', { name: /update available - click to sync/i });
+ fireEvent.click(syncButton);
+
+ // Should open compare preview modal
+ expect(screen.getByRole('heading', { name: /preview changes: unit name/i })).toBeInTheDocument();
+ expect(screen.getByText('Preview not available')).toBeInTheDocument();
+
+ // Click on accept changes
+ const acceptChangesButton = screen.getByText(/accept changes/i);
+ fireEvent.click(acceptChangesButton);
+
+ await waitFor(() => expect(mockUseAcceptLibraryBlockChanges).toHaveBeenCalled());
+ });
+
+ it('should decline sync unit changes from upstream', async () => {
+ renderComponent();
+
+ expect(await screen.findByTestId('unit-card-header')).toBeInTheDocument();
+
+ // Click on sync button
+ const syncButton = screen.getByRole('button', { name: /update available - click to sync/i });
+ fireEvent.click(syncButton);
+
+ // Should open compare preview modal
+ expect(screen.getByRole('heading', { name: /preview changes: unit name/i })).toBeInTheDocument();
+ expect(screen.getByText('Preview not available')).toBeInTheDocument();
+
+ // Click on ignore changes
+ const ignoreChangesButton = screen.getByRole('button', { name: /ignore changes/i });
+ fireEvent.click(ignoreChangesButton);
+
+ // Should open the confirmation modal
+ expect(screen.getByRole('heading', { name: /ignore these changes\?/i })).toBeInTheDocument();
+
+ // Click on ignore button
+ const ignoreButton = screen.getByRole('button', { name: /ignore/i });
+ fireEvent.click(ignoreButton);
+
+ await waitFor(() => expect(mockUseIgnoreLibraryBlockChanges).toHaveBeenCalled());
+ });
});
diff --git a/src/course-unit/preview-changes/index.test.tsx b/src/course-unit/preview-changes/index.test.tsx
index a67fe6a7d2..d28f02575f 100644
--- a/src/course-unit/preview-changes/index.test.tsx
+++ b/src/course-unit/preview-changes/index.test.tsx
@@ -12,13 +12,13 @@ import IframePreviewLibraryXBlockChanges, { LibraryChangesMessageData } from '.'
import { messageTypes } from '../constants';
import { libraryBlockChangesUrl } from '../data/api';
import { ToastActionData } from '../../generic/toast-context';
-import { getLibraryBlockMetadataUrl } from '../../library-authoring/data/api';
+import { getLibraryBlockMetadataUrl, getLibraryContainerApiUrl } from '../../library-authoring/data/api';
const usageKey = 'some-id';
const defaultEventData: LibraryChangesMessageData = {
displayName: 'Test block',
downstreamBlockId: usageKey,
- upstreamBlockId: 'some-lib-id',
+ upstreamBlockId: 'lct:org:lib1:unit:1',
upstreamBlockVersionSynced: 1,
isVertical: false,
};
@@ -87,6 +87,15 @@ describe('', () => {
expect(await screen.findByText('Preview changes: Test block -> New test block')).toBeInTheDocument();
});
+ it('renders both new and old title if they are different on units', async () => {
+ axiosMock.onGet(getLibraryContainerApiUrl(defaultEventData.upstreamBlockId)).reply(200, {
+ displayName: 'New test Unit',
+ });
+ render({ ...defaultEventData, isVertical: true, displayName: 'Test Unit' });
+
+ expect(await screen.findByText('Preview changes: Test Unit -> New test Unit')).toBeInTheDocument();
+ });
+
it('accept changes works', async () => {
axiosMock.onPost(libraryBlockChangesUrl(usageKey)).reply(200, {});
render();
diff --git a/src/course-unit/preview-changes/index.tsx b/src/course-unit/preview-changes/index.tsx
index c26550eb12..157aaeab0d 100644
--- a/src/course-unit/preview-changes/index.tsx
+++ b/src/course-unit/preview-changes/index.tsx
@@ -14,7 +14,7 @@ import messages from './messages';
import { ToastContext } from '../../generic/toast-context';
import LoadingButton from '../../generic/loading-button';
import Loading from '../../generic/Loading';
-import { useLibraryBlockMetadata } from '../../library-authoring/data/apiHooks';
+import { useContainer, useLibraryBlockMetadata } from '../../library-authoring/data/apiHooks';
export interface LibraryChangesMessageData {
displayName: string,
@@ -48,14 +48,20 @@ export const PreviewLibraryXBlockChanges = ({
// ignore changes confirmation modal toggle.
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useToggle(false);
+
+ // TODO: Split into two different components to avoid making these two calls in which
+ // one will definitely fail
const { data: componentMetadata } = useLibraryBlockMetadata(blockData?.upstreamBlockId);
+ const { data: unitMetadata } = useContainer(blockData?.upstreamBlockId);
+
+ const metadata = blockData?.isVertical ? unitMetadata : componentMetadata;
const acceptChangesMutation = useAcceptLibraryBlockChanges();
const ignoreChangesMutation = useIgnoreLibraryBlockChanges();
const getTitle = useCallback(() => {
const oldName = blockData?.displayName;
- const newName = componentMetadata?.displayName;
+ const newName = metadata?.displayName;
if (!oldName) {
if (blockData?.isVertical) {
@@ -67,7 +73,7 @@ export const PreviewLibraryXBlockChanges = ({
return intl.formatMessage(messages.title, { blockTitle: oldName });
}
return intl.formatMessage(messages.diffTitle, { oldName, newName });
- }, [blockData, componentMetadata]);
+ }, [blockData, metadata]);
const getBody = useCallback(() => {
if (!blockData) {
@@ -78,6 +84,7 @@ export const PreviewLibraryXBlockChanges = ({
usageKey={blockData.upstreamBlockId}
oldVersion={blockData.upstreamBlockVersionSynced || 'published'}
newVersion="published"
+ isContainer={blockData.isVertical}
/>
);
}, [blockData]);
diff --git a/src/library-authoring/component-comparison/CompareChangesWidget.tsx b/src/library-authoring/component-comparison/CompareChangesWidget.tsx
index 19581e5c56..aaca0df279 100644
--- a/src/library-authoring/component-comparison/CompareChangesWidget.tsx
+++ b/src/library-authoring/component-comparison/CompareChangesWidget.tsx
@@ -6,10 +6,21 @@ import { LibraryBlock, type VersionSpec } from '../LibraryBlock';
import messages from './messages';
+const PreviewNotAvailable = () => {
+ const intl = useIntl();
+
+ return (
+
+ {intl.formatMessage(messages.previewNotAvailable)}
+
+ );
+};
+
interface Props {
usageKey: string;
oldVersion?: VersionSpec;
newVersion?: VersionSpec;
+ isContainer?: boolean;
}
/**
@@ -20,7 +31,12 @@ interface Props {
* In the future, it would be better to have a way of highlighting the changes
* or showing a diff.
*/
-const CompareChangesWidget = ({ usageKey, oldVersion = 'published', newVersion = 'draft' }: Props) => {
+const CompareChangesWidget = ({
+ usageKey,
+ oldVersion = 'published',
+ newVersion = 'draft',
+ isContainer = false,
+}: Props) => {
const intl = useIntl();
return (
@@ -28,24 +44,28 @@ const CompareChangesWidget = ({ usageKey, oldVersion = 'published', newVersion =
-
-
-
+ {isContainer ? (
) : (
+
+
+
+ )}
-
-
-
+ {isContainer ? (
) : (
+
+
+
+ )}
diff --git a/src/library-authoring/component-comparison/messages.ts b/src/library-authoring/component-comparison/messages.ts
index 89275918ad..322eaee3a4 100644
--- a/src/library-authoring/component-comparison/messages.ts
+++ b/src/library-authoring/component-comparison/messages.ts
@@ -17,6 +17,11 @@ const messages = defineMessages({
defaultMessage: 'Compare Changes',
description: 'Title used for the compare changes dialog',
},
+ previewNotAvailable: {
+ id: 'course-authoring.library-authoring.component-comparison.preview-not-available',
+ defaultMessage: 'Preview not available',
+ description: 'Message shown when preview is not available.',
+ },
});
export default messages;