diff --git a/src/index.jsx b/src/index.jsx index 34f27f1b9..c885dac16 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -35,7 +35,13 @@ import { ToastProvider } from './generic/toast-context'; import 'react-datepicker/dist/react-datepicker.css'; import './index.scss'; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 60_000, // If cache is up to one hour old, no need to re-fetch + }, + }, +}); const App = () => { useEffect(() => { diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx index 2b55375fd..ed02d68bd 100644 --- a/src/library-authoring/LibraryAuthoringPage.test.tsx +++ b/src/library-authoring/LibraryAuthoringPage.test.tsx @@ -111,6 +111,10 @@ describe('', () => { expect(screen.getAllByText('Recently Modified').length).toEqual(1); expect((await screen.findAllByText('Introduction to Testing'))[0]).toBeInTheDocument(); + // Search box should not have focus on page load + const searchBox = screen.getByRole('searchbox'); + expect(searchBox).not.toHaveFocus(); + // Navigate to the components tab fireEvent.click(screen.getByRole('tab', { name: 'Components' })); // "Recently Modified" default sort shown diff --git a/src/library-authoring/LibraryLayout.tsx b/src/library-authoring/LibraryLayout.tsx index c093af7ad..add33f95a 100644 --- a/src/library-authoring/LibraryLayout.tsx +++ b/src/library-authoring/LibraryLayout.tsx @@ -2,11 +2,12 @@ import { useCallback } from 'react'; import { Route, Routes, + useMatch, useParams, - useLocation, + type PathMatch, } from 'react-router-dom'; -import { ROUTES } from './routes'; +import { BASE_ROUTE, ROUTES } from './routes'; import LibraryAuthoringPage from './LibraryAuthoringPage'; import { LibraryProvider } from './common/context/LibraryContext'; import { SidebarProvider } from './common/context/SidebarContext'; @@ -23,12 +24,15 @@ const LibraryLayout = () => { throw new Error('Error: route is missing libraryId.'); } - const location = useLocation(); + // The top-level route is `${BASE_ROUTE}/*`, so match will always be non-null. + const match = useMatch(`${BASE_ROUTE}${ROUTES.COLLECTION}`) as PathMatch<'libraryId' | 'collectionId'> | null; + const collectionId = match?.params.collectionId; + const context = useCallback((childPage) => ( { - ), [location.pathname]); + ), [collectionId]); return ( diff --git a/src/search-manager/SearchKeywordsField.tsx b/src/search-manager/SearchKeywordsField.tsx index 14a6a06dc..90c09fdd9 100644 --- a/src/search-manager/SearchKeywordsField.tsx +++ b/src/search-manager/SearchKeywordsField.tsx @@ -7,7 +7,11 @@ import { useSearchContext } from './SearchManager'; /** * The "main" input field where users type in search keywords. The search happens as they type (no need to press enter). */ -const SearchKeywordsField: React.FC<{ className?: string, placeholder?: string }> = (props) => { +const SearchKeywordsField: React.FC<{ + className?: string, + placeholder?: string, + autoFocus?: boolean, +}> = (props) => { const intl = useIntl(); const { searchKeywords, setSearchKeywords, usageKey } = useSearchContext(); const defaultPlaceholder = usageKey ? messages.clearUsageKeyToSearch : messages.inputPlaceholder; @@ -24,7 +28,7 @@ const SearchKeywordsField: React.FC<{ className?: string, placeholder?: string } > diff --git a/src/search-modal/SearchModal.test.tsx b/src/search-modal/SearchModal.test.tsx index ef3572639..9449a9efd 100644 --- a/src/search-modal/SearchModal.test.tsx +++ b/src/search-modal/SearchModal.test.tsx @@ -73,4 +73,14 @@ describe('', () => { const { findByText } = render(); expect(await findByText('An error occurred. Unable to load search results.')).toBeInTheDocument(); }); + + it('should set focus on the search input box when loaded in the modal', async () => { + axiosMock.onGet(getContentSearchConfigUrl()).replyOnce(200, { + url: 'https://meilisearch.example.com', + index: 'test-index', + apiKey: 'test-api-key', + }); + const { getByRole } = render(); + expect(getByRole('searchbox')).toHaveFocus(); + }); }); diff --git a/src/search-modal/SearchModal.tsx b/src/search-modal/SearchModal.tsx index 2e552fb6e..197ba6c70 100644 --- a/src/search-modal/SearchModal.tsx +++ b/src/search-modal/SearchModal.tsx @@ -21,7 +21,11 @@ const SearchModal: React.FC<{ courseId?: string, isOpen: boolean, onClose: () => isFullscreenOnMobile className="courseware-search-modal" > - + ); }; diff --git a/src/search-modal/SearchUI.tsx b/src/search-modal/SearchUI.tsx index 2df074111..a70e1f69f 100644 --- a/src/search-modal/SearchUI.tsx +++ b/src/search-modal/SearchUI.tsx @@ -19,7 +19,11 @@ import EmptyStates from './EmptyStates'; import SearchResults from './SearchResults'; import messages from './messages'; -const SearchUI: React.FC<{ courseId?: string, closeSearchModal?: () => void }> = (props) => { +const SearchUI: React.FC<{ + courseId?: string, + autoFocus?: boolean, + closeSearchModal?: () => void, +}> = (props) => { const hasCourseId = Boolean(props.courseId); const [searchThisCourseEnabled, setSearchThisCourse] = React.useState(hasCourseId); const switchToThisCourse = React.useCallback(() => setSearchThisCourse(true), []); @@ -39,7 +43,10 @@ const SearchUI: React.FC<{ courseId?: string, closeSearchModal?: () => void }> =
- +