diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx
index cded3d334..f0c0404c9 100644
--- a/src/library-authoring/LibraryAuthoringPage.test.tsx
+++ b/src/library-authoring/LibraryAuthoringPage.test.tsx
@@ -370,7 +370,7 @@ describe('', () => {
fireEvent.change(searchBox, { target: { value: 'words to find' } });
// Default sort option changes to "Most Relevant"
- expect(screen.getAllByText('Most Relevant').length).toEqual(2);
+ expect((await screen.findAllByText('Most Relevant')).length).toEqual(2);
await waitFor(() => {
expect(fetchMock).toHaveBeenLastCalledWith(searchEndpoint, {
body: expect.stringContaining('"sort":[]'),
diff --git a/src/library-authoring/collections/CollectionInfo.tsx b/src/library-authoring/collections/CollectionInfo.tsx
index 277a6fc1c..68d2dcb07 100644
--- a/src/library-authoring/collections/CollectionInfo.tsx
+++ b/src/library-authoring/collections/CollectionInfo.tsx
@@ -48,7 +48,8 @@ const CollectionInfo = () => {
if (componentPickerMode) {
setCollectionId(collectionId);
} else {
- navigateTo({ collectionId });
+ /* istanbul ignore next */
+ navigateTo({ collectionId, doubleClicked: true });
}
}, [componentPickerMode, navigateTo]);
diff --git a/src/library-authoring/collections/LibraryCollectionPage.test.tsx b/src/library-authoring/collections/LibraryCollectionPage.test.tsx
index 3c3bf4547..eb102d2ed 100644
--- a/src/library-authoring/collections/LibraryCollectionPage.test.tsx
+++ b/src/library-authoring/collections/LibraryCollectionPage.test.tsx
@@ -315,7 +315,7 @@ describe('', () => {
fireEvent.change(searchBox, { target: { value: 'words to find' } });
// Default sort option changes to "Most Relevant"
- expect(screen.getAllByText('Most Relevant').length).toEqual(2);
+ expect((await screen.findAllByText('Most Relevant')).length).toEqual(2);
await waitFor(() => {
expect(fetchMock).toHaveBeenLastCalledWith(searchEndpoint, {
body: expect.stringContaining('"sort":[]'),
diff --git a/src/library-authoring/components/BaseCard.tsx b/src/library-authoring/components/BaseCard.tsx
index 591fc3be6..ef7dc5828 100644
--- a/src/library-authoring/components/BaseCard.tsx
+++ b/src/library-authoring/components/BaseCard.tsx
@@ -22,7 +22,7 @@ type BaseCardProps = {
tags: ContentHitTags;
actions: React.ReactNode;
hasUnpublishedChanges?: boolean;
- onSelect: () => void;
+ onSelect: (e?: React.MouseEvent) => void;
selected?: boolean;
};
diff --git a/src/library-authoring/components/CollectionCard.tsx b/src/library-authoring/components/CollectionCard.tsx
index 1f158808f..21d5ef5d4 100644
--- a/src/library-authoring/components/CollectionCard.tsx
+++ b/src/library-authoring/components/CollectionCard.tsx
@@ -114,7 +114,7 @@ type CollectionCardProps = {
const CollectionCard = ({ hit } : CollectionCardProps) => {
const { componentPickerMode } = useComponentPickerContext();
- const { showOnlyPublished } = useLibraryContext();
+ const { showOnlyPublished, setCollectionId } = useLibraryContext();
const { openCollectionInfoSidebar, sidebarComponentInfo } = useSidebarContext();
const {
@@ -136,11 +136,15 @@ const CollectionCard = ({ hit } : CollectionCardProps) => {
&& sidebarComponentInfo.id === collectionId;
const { navigateTo } = useLibraryRoutes();
- const openCollection = useCallback(() => {
+ const openCollection = useCallback((e?: React.MouseEvent) => {
openCollectionInfoSidebar(collectionId);
+ const doubleClicked = (e?.detail || 0) > 1;
if (!componentPickerMode) {
- navigateTo({ collectionId });
+ navigateTo({ collectionId, doubleClicked });
+ } else if (doubleClicked) {
+ /* istanbul ignore next */
+ setCollectionId(collectionId);
}
}, [collectionId, navigateTo, openCollectionInfoSidebar]);
diff --git a/src/library-authoring/components/ComponentCard.test.tsx b/src/library-authoring/components/ComponentCard.test.tsx
index 087b0ad20..e0e2c44e2 100644
--- a/src/library-authoring/components/ComponentCard.test.tsx
+++ b/src/library-authoring/components/ComponentCard.test.tsx
@@ -11,6 +11,13 @@ import { ContentHit } from '../../search-manager';
import ComponentCard from './ComponentCard';
import { PublishStatus } from '../../search-manager/data/api';
+const mockNavigate = jest.fn();
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'), // use actual for all non-hook parts
+ useNavigate: () => mockNavigate,
+}));
+
const contentHit: ContentHit = {
id: '1',
usageKey: 'lb:org1:demolib:html:a1fa8bdd-dc67-4976-9bf5-0ea75a9bca3d',
@@ -41,6 +48,8 @@ const contentHit: ContentHit = {
const libraryId = 'lib:org1:Demo_Course';
const render = () => baseRender(, {
+ path: '/library/:libraryId',
+ params: { libraryId },
extraWrapper: ({ children }) => (
{ children }
@@ -104,4 +113,24 @@ describe('', () => {
expect(mockShowToast).toHaveBeenCalledWith('Error copying to clipboard');
});
});
+
+ it('should select component on clicking edit menu option', async () => {
+ initializeMocks();
+ render();
+
+ // Open menu
+ const menu = await screen.findByTestId('component-card-menu-toggle');
+ expect(menu).toBeInTheDocument();
+ fireEvent.click(menu);
+
+ // Click copy to clipboard
+ const editOption = await screen.findByRole('button', { name: 'Edit' });
+ expect(editOption).toBeInTheDocument();
+ fireEvent.click(editOption);
+ // Verify that the url is updated to component url i.e. component is selected
+ expect(mockNavigate).toHaveBeenCalledWith({
+ pathname: `/library/${libraryId}/component/${contentHit.usageKey}`,
+ search: '',
+ });
+ });
});
diff --git a/src/library-authoring/components/ComponentMenu.tsx b/src/library-authoring/components/ComponentMenu.tsx
index e3d0afe64..ba3711711 100644
--- a/src/library-authoring/components/ComponentMenu.tsx
+++ b/src/library-authoring/components/ComponentMenu.tsx
@@ -90,6 +90,12 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
});
};
+ const handleEdit = useCallback(() => {
+ navigateTo({ componentId: usageKey });
+ openComponentInfoSidebar(usageKey);
+ openComponentEditor(usageKey);
+ }, [usageKey]);
+
const scheduleJumpToCollection = useRunOnNextRender(() => {
// TODO: Ugly hack to make sure sidebar shows add to collection section
// This needs to run after all changes to url takes place to avoid conflicts.
@@ -119,7 +125,7 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
data-testid="component-card-menu-toggle"
/>
- openComponentEditor(usageKey) } : { disabled: true })}>
+
diff --git a/src/library-authoring/components/ContainerCard.tsx b/src/library-authoring/components/ContainerCard.tsx
index a6442b1b5..ebcbbbeaf 100644
--- a/src/library-authoring/components/ContainerCard.tsx
+++ b/src/library-authoring/components/ContainerCard.tsx
@@ -204,12 +204,12 @@ const ContainerCard = ({ hit } : ContainerCardProps) => {
const { navigateTo } = useLibraryRoutes();
- const openContainer = useCallback(() => {
+ const openContainer = useCallback((e?: React.MouseEvent) => {
if (itemType === 'unit') {
openUnitInfoSidebar(unitId);
setUnitId(unitId);
if (!componentPickerMode) {
- navigateTo({ unitId });
+ navigateTo({ unitId, doubleClicked: (e?.detail || 0) > 1 });
}
}
}, [unitId, itemType, openUnitInfoSidebar, navigateTo]);
diff --git a/src/library-authoring/containers/UnitInfo.test.tsx b/src/library-authoring/containers/UnitInfo.test.tsx
index 9b154d13c..c44d19604 100644
--- a/src/library-authoring/containers/UnitInfo.test.tsx
+++ b/src/library-authoring/containers/UnitInfo.test.tsx
@@ -19,23 +19,28 @@ mockGetContainerChildren.applyMock();
const { libraryId } = mockContentLibrary;
const { containerId } = mockGetContainerMetadata;
-const render = (showOnlyPublished: boolean = false) => baseRender(, {
- extraWrapper: ({ children }) => (
-
- {
+ const params: { libraryId: string, unitId?: string } = { libraryId, unitId: containerId };
+ return baseRender(, {
+ path: '/library/:libraryId/:unitId?',
+ params,
+ extraWrapper: ({ children }) => (
+
- {children}
-
-
- ),
-});
+
+ {children}
+
+
+ ),
+ });
+};
let axiosMock: MockAdapter;
let mockShowToast;
diff --git a/src/library-authoring/data/api.test.ts b/src/library-authoring/data/api.test.ts
index f5882ee67..ece0773f4 100644
--- a/src/library-authoring/data/api.test.ts
+++ b/src/library-authoring/data/api.test.ts
@@ -142,4 +142,13 @@ describe('library data API', () => {
await api.removeLibraryContainerChildren(containerId, ['test']);
expect(axiosMock.history.delete[0].url).toEqual(url);
});
+
+ it('getContentLibraryV2List', async () => {
+ const url = api.getContentLibraryV2ListApiUrl();
+
+ axiosMock.onGet(url).reply(200, { some: 'data' });
+
+ await api.getContentLibraryV2List({ type: 'complex' });
+ expect(axiosMock.history.get[0].url).toEqual(url);
+ });
});
diff --git a/src/library-authoring/routes.ts b/src/library-authoring/routes.ts
index 510368456..4615f229d 100644
--- a/src/library-authoring/routes.ts
+++ b/src/library-authoring/routes.ts
@@ -49,6 +49,7 @@ export type NavigateToData = {
collectionId?: string,
contentType?: ContentType,
unitId?: string,
+ doubleClicked?: boolean,
};
export type LibraryRoutesData = {
@@ -80,6 +81,7 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
collectionId,
unitId,
contentType,
+ doubleClicked,
}: NavigateToData = {}) => {
const {
collectionId: urlCollectionId,
@@ -125,7 +127,7 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
} else if (insideCollections) {
// We're inside the Collections tab,
route = (
- (collectionId && collectionId === (urlCollectionId || urlSelectedItemId))
+ (collectionId && doubleClicked)
// now open the previously-selected collection,
? ROUTES.COLLECTION
// or stay there to list all collections, or a selected collection.
@@ -142,7 +144,7 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
} else if (insideUnits) {
// We're inside the units tab,
route = (
- (unitId && unitId === (urlUnitId || urlSelectedItemId))
+ (unitId && doubleClicked)
// now open the previously-selected unit,
? ROUTES.UNIT
// or stay there to list all units, or a selected unit.
@@ -156,10 +158,10 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
// We're inside the All Content tab, so stay there,
// and select a component.
route = ROUTES.COMPONENT;
- } else if (collectionId && collectionId === (urlCollectionId || urlSelectedItemId)) {
+ } else if (collectionId && doubleClicked) {
// now open the previously-selected collection
route = ROUTES.COLLECTION;
- } else if (unitId && unitId === (urlUnitId || urlSelectedItemId)) {
+ } else if (unitId && doubleClicked) {
// now open the previously-selected unit
route = ROUTES.UNIT;
} else {
diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx
index 4d42bf2a4..ef7d62e1a 100644
--- a/src/library-authoring/units/LibraryUnitBlocks.tsx
+++ b/src/library-authoring/units/LibraryUnitBlocks.tsx
@@ -238,9 +238,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
const [hidePreviewFor, setHidePreviewFor] = useState(null);
const { showToast } = useContext(ToastContext);
- const { readOnly, showOnlyPublished } = useLibraryContext();
- const { sidebarComponentInfo } = useSidebarContext();
- const unitId = sidebarComponentInfo?.id;
+ const { unitId, readOnly, showOnlyPublished } = useLibraryContext();
const { openAddContentSidebar } = useSidebarContext();
diff --git a/src/search-manager/SearchKeywordsField.tsx b/src/search-manager/SearchKeywordsField.tsx
index 90c09fdd9..bbe3b67f8 100644
--- a/src/search-manager/SearchKeywordsField.tsx
+++ b/src/search-manager/SearchKeywordsField.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { SearchField } from '@openedx/paragon';
+import { debounce } from 'lodash';
import messages from './messages';
import { useSearchContext } from './SearchManager';
@@ -17,10 +18,15 @@ const SearchKeywordsField: React.FC<{
const defaultPlaceholder = usageKey ? messages.clearUsageKeyToSearch : messages.inputPlaceholder;
const { placeholder = intl.formatMessage(defaultPlaceholder) } = props;
+ const handleSearch = React.useCallback(
+ debounce((term) => setSearchKeywords(term.trim()), 400),
+ [searchKeywords],
+ );// Perform search after 500ms
+
return (
setSearchKeywords('')}
value={searchKeywords}
className={props.className}