Skip to content

Commit

Permalink
fix: new block editor refresh in collection page
Browse files Browse the repository at this point in the history
Link component to collection after closing editor fix re-render of
editor in collections page on link mutation.
  • Loading branch information
navinkarkera committed Nov 9, 2024
1 parent ca88644 commit 3f95ab6
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 11 deletions.
67 changes: 65 additions & 2 deletions src/library-authoring/add-content/AddContentContainer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { snakeCaseObject } from '@edx/frontend-platform';
import {
fireEvent,
render as baseRender,
Expand All @@ -10,9 +11,14 @@ import { getCreateLibraryBlockUrl, getLibraryCollectionComponentApiUrl, getLibra
import { mockBroadcastChannel, mockClipboardEmpty, mockClipboardHtml } from '../../generic/data/api.mock';
import { LibraryProvider } from '../common/context';
import AddContentContainer from './AddContentContainer';
import { ComponentEditorModal } from '../components/ComponentEditorModal';
import editorCmsApi from '../../editors/data/services/cms/api';

mockBroadcastChannel();

// Mocks for ComponentEditorModal to work in tests.
jest.mock('frontend-components-tinymce-advanced-plugins', () => ({ a11ycheckerCss: '' }));

const { libraryId } = mockContentLibrary;
const render = (collectionId?: string) => {
const params: { libraryId: string, collectionId?: string } = { libraryId };
Expand All @@ -26,13 +32,18 @@ const render = (collectionId?: string) => {
<LibraryProvider
libraryId={libraryId}
collectionId={collectionId}
>{ children }
>
{ children }
<ComponentEditorModal />
</LibraryProvider>
),
});
};

describe('<AddContentContainer />', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should render content buttons', () => {
initializeMocks();
mockClipboardEmpty.applyMock();
Expand Down Expand Up @@ -62,7 +73,7 @@ describe('<AddContentContainer />', () => {
await waitFor(() => expect(axiosMock.history.patch.length).toEqual(0));
});

it('should create a content in a collection', async () => {
it('should create a content in a collection for non-editable blocks', async () => {
const { axiosMock } = initializeMocks();
mockClipboardEmpty.applyMock();
const collectionId = 'some-collection-id';
Expand All @@ -71,6 +82,7 @@ describe('<AddContentContainer />', () => {
libraryId,
collectionId,
);
// having id of block which is not video, html or problem will not trigger editor.
axiosMock.onPost(url).reply(200, { id: 'some-component-id' });
axiosMock.onPatch(collectionComponentUrl).reply(200);

Expand All @@ -84,6 +96,57 @@ describe('<AddContentContainer />', () => {
await waitFor(() => expect(axiosMock.history.patch[0].url).toEqual(collectionComponentUrl));
});

it('should create a content in a collection for editable blocks', async () => {
const { axiosMock } = initializeMocks();
mockClipboardEmpty.applyMock();
const collectionId = 'some-collection-id';
const url = getCreateLibraryBlockUrl(libraryId);
const collectionComponentUrl = getLibraryCollectionComponentApiUrl(
libraryId,
collectionId,
);
// Mocks for ComponentEditorModal to work in tests.
jest.spyOn(editorCmsApi, 'fetchImages').mockImplementation(async () => ( // eslint-disable-next-line
{ data: { assets: [], start: 0, end: 0, page: 0, pageSize: 50, totalCount: 0 } }
));
jest.spyOn(editorCmsApi, 'fetchByUnitId').mockImplementation(async () => ({
status: 200,
data: {
ancestors: [{
id: 'block-v1:Org+TS100+24+type@vertical+block@parent',
display_name: 'You-Knit? The Test Unit',
category: 'vertical',
has_children: true,
}],
},
}));

axiosMock.onPost(url).reply(200, {
id: 'lb:OpenedX:CSPROB2:html:1a5efd56-4ee5-4df0-b466-44f08fbbf567',
});
const fieldsHtml = {
displayName: 'Introduction to Testing',
data: '<p>This is a text component which uses <strong>HTML</strong>.</p>',
metadata: { displayName: 'Introduction to Testing' },
};
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => (
{ status: 200, data: snakeCaseObject(fieldsHtml) }
));
axiosMock.onPatch(collectionComponentUrl).reply(200);

render(collectionId);

const textButton = screen.getByRole('button', { name: /text/i });
fireEvent.click(textButton);

// Component should be linked to Collection on closing editor.
const closeButton = await screen.findByRole('button', { name: 'Exit the editor' });
fireEvent.click(closeButton);
await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(url));
await waitFor(() => expect(axiosMock.history.patch.length).toEqual(1));
await waitFor(() => expect(axiosMock.history.patch[0].url).toEqual(collectionComponentUrl));
});

it('should render paste button if clipboard contains pastable xblock', async () => {
initializeMocks();
// Simulate having an HTML block in the clipboard:
Expand Down
5 changes: 3 additions & 2 deletions src/library-authoring/add-content/AddContentContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,14 @@ const AddContentContainer = () => {
blockType,
definitionId: `${uuid4()}`,
}).then((data) => {
linkComponent(data.id);
const hasEditor = canEditComponent(data.id);
if (hasEditor) {
openComponentEditor(data.id);
// linkComponent on editor close.
openComponentEditor(data.id, () => linkComponent(data.id));
} else {
// We can't start editing this right away so just show a toast message:
showToast(intl.formatMessage(messages.successCreateMessage));
linkComponent(data.id);
}
}).catch((error) => {
showToast(parseErrorMsg(
Expand Down
21 changes: 17 additions & 4 deletions src/library-authoring/common/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ export interface SidebarComponentInfo {
additionalAction?: SidebarAdditionalActions;
}

export interface ComponentEditorInfo {
usageKey: string;
onClose?: () => void;
}

export enum SidebarAdditionalActions {
JumpToAddCollections = 'jump-to-add-collections',
}
Expand Down Expand Up @@ -100,8 +105,8 @@ export type LibraryContextData = {
openCollectionInfoSidebar: (collectionId: string, additionalAction?: SidebarAdditionalActions) => void;
// Editor modal - for editing some component
/** If the editor is open and the user is editing some component, this is its usageKey */
componentBeingEdited: string | undefined;
openComponentEditor: (usageKey: string) => void;
componentBeingEdited: ComponentEditorInfo | undefined;
openComponentEditor: (usageKey: string, onClose?: () => void) => void;
closeComponentEditor: () => void;
resetSidebarAdditionalActions: () => void;
} & ComponentPickerType;
Expand Down Expand Up @@ -174,8 +179,16 @@ export const LibraryProvider = ({
);
const [isLibraryTeamModalOpen, openLibraryTeamModal, closeLibraryTeamModal] = useToggle(false);
const [isCreateCollectionModalOpen, openCreateCollectionModal, closeCreateCollectionModal] = useToggle(false);
const [componentBeingEdited, openComponentEditor] = useState<string | undefined>();
const closeComponentEditor = useCallback(() => openComponentEditor(undefined), []);
const [componentBeingEdited, setComponentBeingEdited] = useState<ComponentEditorInfo | undefined>();
const closeComponentEditor = useCallback(() => {
setComponentBeingEdited((prev) => {
prev?.onClose?.();
return undefined;
});
}, []);
const openComponentEditor = useCallback((usageKey: string, onClose?: () => void) => {
setComponentBeingEdited({ usageKey, onClose });
}, []);

const [selectedComponents, setSelectedComponents] = useState<SelectedComponent[]>([]);

Expand Down
6 changes: 3 additions & 3 deletions src/library-authoring/components/ComponentEditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ export const ComponentEditorModal: React.FC<Record<never, never>> = () => {
if (componentBeingEdited === undefined) {
return null;
}
const blockType = getBlockType(componentBeingEdited);
const blockType = getBlockType(componentBeingEdited.usageKey);

const onClose = () => {
closeComponentEditor();
invalidateComponentData(queryClient, libraryId, componentBeingEdited);
invalidateComponentData(queryClient, libraryId, componentBeingEdited.usageKey);
};

return (
<EditorPage
courseId={libraryId}
blockType={blockType}
blockId={componentBeingEdited}
blockId={componentBeingEdited.usageKey}
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
lmsEndpointUrl={getConfig().LMS_BASE_URL}
onClose={onClose}
Expand Down

0 comments on commit 3f95ab6

Please sign in to comment.