diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx
index ccb1e87c42..1dfe2d4186 100644
--- a/src/library-authoring/LibraryAuthoringPage.test.tsx
+++ b/src/library-authoring/LibraryAuthoringPage.test.tsx
@@ -46,6 +46,21 @@ const returnEmptyResult = (_url, req) => {
return mockEmptyResult;
};
+const returnLowNumberResults = (_url, req) => {
+ const requestData = JSON.parse(req.body?.toString() ?? '');
+ const query = requestData?.queries[0]?.q ?? '';
+ // We have to replace the query (search keywords) in the mock results with the actual query,
+ // because otherwise we may have an inconsistent state that causes more queries and unexpected results.
+ mockResult.results[0].query = query;
+ // Limit number of results to just 2
+ mockResult.results[0].hits = mockResult.results[0]?.hits.slice(0, 2);
+ mockResult.results[0].estimatedTotalHits = 2;
+ // And fake the required '_formatted' fields; it contains the highlighting ... around matched words
+ // eslint-disable-next-line no-underscore-dangle, no-param-reassign
+ mockResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; });
+ return mockResult;
+};
+
const libraryData = {
id: 'lib:org1:lib1',
type: 'complex',
@@ -152,8 +167,10 @@ describe('', () => {
getByRole, getByText, queryByText, getAllByText,
} = render();
- // Ensure the search endpoint is called
- await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); });
+ // Ensure the search endpoint is called:
+ // Call 1: To fetch searchable/filterable/sortable library data
+ // Call 2: To fetch the recently modified components only
+ await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); });
expect(getByText('Content library')).toBeInTheDocument();
expect(getByText(libraryData.title)).toBeInTheDocument();
@@ -197,8 +214,10 @@ describe('', () => {
expect(await findByText('Content library')).toBeInTheDocument();
expect(await findByText(libraryData.title)).toBeInTheDocument();
- // Ensure the search endpoint is called
- await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); });
+ // Ensure the search endpoint is called:
+ // Call 1: To fetch searchable/filterable/sortable library data
+ // Call 2: To fetch the recently modified components only
+ await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); });
expect(getByText('You have not added any content to this library yet.')).toBeInTheDocument();
});
@@ -213,13 +232,16 @@ describe('', () => {
expect(await findByText('Content library')).toBeInTheDocument();
expect(await findByText(libraryData.title)).toBeInTheDocument();
- // Ensure the search endpoint is called
- await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); });
+ // Ensure the search endpoint is called:
+ // Call 1: To fetch searchable/filterable/sortable library data
+ // Call 2: To fetch the recently modified components only
+ await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); });
fireEvent.change(getByRole('searchbox'), { target: { value: 'noresults' } });
- // Ensure the search endpoint is called again
- await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); });
+ // Ensure the search endpoint is called again, only once more since the recently modified call
+ // should not be impacted by the search
+ await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(3, searchEndpoint, 'post'); });
expect(getByText('No matching components found in this library.')).toBeInTheDocument();
@@ -231,4 +253,75 @@ describe('', () => {
// This step is necessary to avoid the url change leak to other tests
fireEvent.click(getByRole('tab', { name: 'Home' }));
});
+
+ it('show the "View All" button when viewing library with many components', async () => {
+ mockUseParams.mockReturnValue({ libraryId: libraryData.id });
+ axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData);
+
+ const {
+ getByRole, getByText, queryByText, getAllByText,
+ } = render();
+
+ // Ensure the search endpoint is called:
+ // Call 1: To fetch searchable/filterable/sortable library data
+ // Call 2: To fetch the recently modified components only
+ await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); });
+
+ expect(getByText('Content library')).toBeInTheDocument();
+ expect(getByText(libraryData.title)).toBeInTheDocument();
+
+ expect(queryByText('You have not added any content to this library yet.')).not.toBeInTheDocument();
+
+ expect(getByText('Recently Modified')).toBeInTheDocument();
+ expect(getByText('Collections (0)')).toBeInTheDocument();
+ expect(getByText('Components (6)')).toBeInTheDocument();
+ expect(getAllByText('Test HTML Block')[0]).toBeInTheDocument();
+
+ // There should only be one "View All" button, since the Components count
+ // are above the preview limit (4)
+ expect(getByText('View All')).toBeInTheDocument();
+
+ // Clicking on "View All" button should navigate to the Components tab
+ fireEvent.click(getByText('View All'));
+ expect(queryByText('Recently Modified')).not.toBeInTheDocument();
+ expect(queryByText('Collections (0)')).not.toBeInTheDocument();
+ expect(queryByText('Components (6)')).not.toBeInTheDocument();
+ expect(getAllByText('Test HTML Block')[0]).toBeInTheDocument();
+
+ // Go back to Home tab
+ // This step is necessary to avoid the url change leak to other tests
+ fireEvent.click(getByRole('tab', { name: 'Home' }));
+ expect(getByText('Recently Modified')).toBeInTheDocument();
+ expect(getByText('Collections (0)')).toBeInTheDocument();
+ expect(getByText('Components (6)')).toBeInTheDocument();
+ });
+
+ it('should not show the "View All" button when viewing library with low number of components', async () => {
+ mockUseParams.mockReturnValue({ libraryId: libraryData.id });
+ axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData);
+ fetchMock.post(searchEndpoint, returnLowNumberResults, { overwriteRoutes: true });
+
+ const {
+ getByText, queryByText, getAllByText,
+ } = render();
+
+ // Ensure the search endpoint is called:
+ // Call 1: To fetch searchable/filterable/sortable library data
+ // Call 2: To fetch the recently modified components only
+ await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); });
+
+ expect(getByText('Content library')).toBeInTheDocument();
+ expect(getByText(libraryData.title)).toBeInTheDocument();
+
+ expect(queryByText('You have not added any content to this library yet.')).not.toBeInTheDocument();
+
+ expect(getByText('Recently Modified')).toBeInTheDocument();
+ expect(getByText('Collections (0)')).toBeInTheDocument();
+ expect(getByText('Components (2)')).toBeInTheDocument();
+ expect(getAllByText('Test HTML Block')[0]).toBeInTheDocument();
+
+ // There should not be any "View All" button on page since Components count
+ // is less than the preview limit (4)
+ expect(queryByText('View All')).not.toBeInTheDocument();
+ });
});
diff --git a/src/library-authoring/messages.ts b/src/library-authoring/messages.ts
index 613e0b9e3c..c7a39a3410 100644
--- a/src/library-authoring/messages.ts
+++ b/src/library-authoring/messages.ts
@@ -78,12 +78,12 @@ const messages = defineMessages({
collectionsSectionTitle: {
id: 'course-authoring.library-authoring.collections-section-title',
defaultMessage: 'Collections ({collectionCount})',
- description: 'Title for the Recently Modified section in library home',
+ description: 'Title for the Collections section in library home',
},
componentsSectionTitle: {
- id: 'course-authoring.library-authoring.recently-modified-section-title',
+ id: 'course-authoring.library-authoring.components-section-title',
defaultMessage: 'Components ({componentCount})',
- description: 'Title for the Recently Modified section in library home',
+ description: 'Title for the Components section in library home',
},
});
diff --git a/src/search-manager/SearchManager.ts b/src/search-manager/SearchManager.ts
index 8c59aed4d0..f947603727 100644
--- a/src/search-manager/SearchManager.ts
+++ b/src/search-manager/SearchManager.ts
@@ -103,7 +103,7 @@ export const SearchContextProvider: React.FC<{
// Note: SearchSortOption.RELEVANCE is special, it means "no custom sorting",
// so we send it to useContentSearchResults as an empty array.
const sort: SearchSortOption[] = (overrideSearchSortOrder && [overrideSearchSortOrder])
- || searchSortOrder === SearchSortOption.RELEVANCE ? [] : [searchSortOrder];
+ || (searchSortOrder === SearchSortOption.RELEVANCE ? [] : [searchSortOrder]);
const canClearFilters = blockTypesFilter.length > 0 || tagsFilter.length > 0;
const clearFilters = React.useCallback(() => {