diff --git a/src/app/content/components/Page.spec.tsx b/src/app/content/components/Page.spec.tsx index 16a35d54ea..787d31b794 100644 --- a/src/app/content/components/Page.spec.tsx +++ b/src/app/content/components/Page.spec.tsx @@ -811,7 +811,6 @@ describe('Page', () => { await new Promise((resolve) => setImmediate(resolve)); expect(mockHighlight.addFocusedStyles).toHaveBeenCalled(); - expect(scrollTo).toHaveBeenCalledWith(highlightElement); }); it('doesn\'t scroll to search result when selected but unchanged', async() => { @@ -906,7 +905,6 @@ describe('Page', () => { expect(highlightResults).toHaveBeenCalledWith(expect.anything(), [hit]); expect(mockHighlight.addFocusedStyles).toHaveBeenCalled(); - expect(scrollTo).toHaveBeenCalledWith(highlightElement); }); it('renders error modal for different search results', async() => { diff --git a/src/app/content/components/Page/searchHighlightManager.ts b/src/app/content/components/Page/searchHighlightManager.ts index 62c77a7ffd..4511f2699a 100644 --- a/src/app/content/components/Page/searchHighlightManager.ts +++ b/src/app/content/components/Page/searchHighlightManager.ts @@ -2,7 +2,6 @@ import Highlighter, { Highlight } from '@openstax/highlighter'; import { HTMLElement } from '@openstax/types/lib.dom'; import isEqual from 'lodash/fp/isEqual'; import { IntlShape } from 'react-intl'; -import { scrollTo } from '../../../domUtils'; import { AppState } from '../../../types'; import { memoizeStateToProps } from '../../../utils'; import * as selectSearch from '../../search/selectors'; @@ -75,10 +74,9 @@ const selectResult = (services: Services, previous: HighlightProp | null, curren allImagesLoaded(services.container).then( () => { const target = selectedElements[0] as HTMLElement; - const container = target.closest('[tabindex]') as HTMLElement; + const focusTarget: HTMLElement | null = target.querySelector('[tabindex="0"]'); - scrollTo(target); - container?.focus(); + focusTarget?.focus(); } ); } diff --git a/src/app/content/components/Topbar/index.spec.tsx b/src/app/content/components/Topbar/index.spec.tsx index 7c3823ab2f..024219d63b 100644 --- a/src/app/content/components/Topbar/index.spec.tsx +++ b/src/app/content/components/Topbar/index.spec.tsx @@ -1,13 +1,21 @@ import React from 'react'; import { Provider } from 'react-redux'; -import renderer from 'react-test-renderer'; +import renderer, { TestRendererOptions } from 'react-test-renderer'; import Topbar from '.'; import createTestServices from '../../../../test/createTestServices'; import createTestStore from '../../../../test/createTestStore'; import MessageProvider from '../../../../test/MessageProvider'; import { book as archiveBook } from '../../../../test/mocks/archiveLoader'; import { mockCmsBook } from '../../../../test/mocks/osWebLoader'; -import { makeEvent, makeFindByTestId, makeFindOrNullByTestId, makeInputEvent } from '../../../../test/reactutils'; +import { + makeEvent, + makeFindByTestId, + makeFindOrNullByTestId, + makeInputEvent, + dispatchKeyDownEvent, + renderToDom, +} from '../../../../test/reactutils'; +import { act } from 'react-dom/test-utils'; import { makeSearchResults } from '../../../../test/searchResults'; import TestContainer from '../../../../test/TestContainer'; import * as Services from '../../../context/Services'; @@ -15,6 +23,8 @@ import { MiddlewareAPI, Store } from '../../../types'; import { assertDocument } from '../../../utils'; import { openMobileMenu, setTextSize } from '../../actions'; import { textResizerMaxValue, textResizerMinValue } from '../../constants'; +import { HTMLElement } from '@openstax/types/lib.dom'; +import { searchKeyCombination } from '../../highlights/constants'; import { clearSearch, closeSearchResultsMobile, @@ -25,9 +35,15 @@ import { import * as searchSelectors from '../../search/selectors'; import { formatBookData } from '../../utils'; import { CloseButtonNew, MenuButton, MobileSearchWrapper, SearchButton, TextResizerMenu } from './styled'; +import { useMatchMobileQuery } from '../../../reactUtils'; const book = formatBookData(archiveBook, mockCmsBook); +jest.mock('../../../reactUtils', () => ({ + ...(jest as any).requireActual('../../../reactUtils'), + useMatchMobileQuery: jest.fn(), +})); + describe('search', () => { let store: Store; let dispatch: jest.SpyInstance; @@ -43,13 +59,17 @@ describe('search', () => { }; }); - const render = () => renderer.create( + const render = (options?: TestRendererOptions) => renderer.create( - ); + , options); + +const dispatchSearchShortcut = (target: HTMLElement | undefined) => { + dispatchKeyDownEvent({code: searchKeyCombination.code, altKey: searchKeyCombination.altKey, target}); +}; it('opens and closes mobile interface', () => { const component = render(); @@ -67,6 +87,67 @@ describe('search', () => { }); expect(mobileSearch.props.mobileToolbarOpen).toBe(false); expect(event.preventDefault).toHaveBeenCalledTimes(2); + + }); + + it('goes between main and search input when no search results', () => { + const {node} = renderToDom( + + + + +
+ + + + ); + const tb = node.querySelector('[class*="TopBar"]'); + + expect(document?.activeElement?.tagName).toBe('INPUT'); + act(() => dispatchSearchShortcut(tb!)); + expect(document?.activeElement?.tagName).toBe('MAIN'); + }); + + it('goes to search results when provided', () => { + const {node} = renderToDom( + + + + +
+
+ + + + ); + const tb = node.querySelector('[class*="TopBar"]'); + + store.dispatch(receiveSearchResults(makeSearchResults())); + + act(() => dispatchSearchShortcut(tb!)); + expect(document?.activeElement?.tagName).toBe('INPUT'); + act(() => dispatchSearchShortcut(tb!)); + expect(document?.activeElement?.classList.contains('SearchResultsBar')).toBe(true); + }); + + it('aborts on mobile', () => { + (useMatchMobileQuery as jest.Mock).mockReturnValue(true); + const {node} = renderToDom( + + + + +
+ + + + ); + const tb = node.querySelector('[class*="TopBar"]'); + + act(() => dispatchSearchShortcut(tb!)); + expect(document?.activeElement?.tagName).toBe('INPUT'); + act(() => dispatchSearchShortcut(tb!)); + expect(document?.activeElement?.tagName).not.toBe('MAIN'); }); it('doesn\'t dispatch search for empty string', () => { @@ -127,7 +208,9 @@ describe('search', () => { const findById = makeFindByTestId(component.root); const inputEvent = makeInputEvent('cool search'); - findById('desktop-search-input').props.onChange(inputEvent); + renderer.act(() => { + findById('desktop-search-input').props.onChange(inputEvent); + }); const event = makeEvent(); renderer.act(() => findById('desktop-search').props.onSubmit(event)); @@ -315,7 +398,9 @@ describe('search button', () => { const findById = makeFindByTestId(component.root); const inputEvent = makeInputEvent('cool search'); - findById('desktop-search-input').props.onChange(inputEvent); + renderer.act( + () => findById('desktop-search-input').props.onChange(inputEvent) + ); const event = makeEvent(); renderer.act(() => findById('desktop-search').props.onSubmit(event)); diff --git a/src/app/content/components/Topbar/index.tsx b/src/app/content/components/Topbar/index.tsx index bc00a374ee..72c4f8605d 100644 --- a/src/app/content/components/Topbar/index.tsx +++ b/src/app/content/components/Topbar/index.tsx @@ -11,7 +11,7 @@ import { clearSearch, openMobileToolbar, openSearchResultsMobile, - requestSearch + requestSearch, } from '../../search/actions'; import * as selectSearch from '../../search/selectors'; import * as selectContent from '../../selectors'; @@ -19,6 +19,9 @@ import { mobileNudgeStudyToolsTargetId } from '../NudgeStudyTools/constants'; import { NudgeElementTarget } from '../NudgeStudyTools/styles'; import * as Styled from './styled'; import { TextResizer } from './TextResizer'; +import { useKeyCombination, useMatchMobileQuery } from '../../../reactUtils'; +import { searchKeyCombination } from '../../highlights/constants'; +import { HTMLElement, HTMLInputElement } from '@openstax/types/lib.dom'; interface Props { search: typeof requestSearch; @@ -35,191 +38,327 @@ interface Props { bookTheme: string; textSize: TextResizerValue | null; setTextSize: (size: TextResizerValue) => void; + selectedResult: any; } -interface State { - query: string; - queryProp: string; - formSubmitted: boolean; +type CommonSearchInputParams = Pick< + Props, + 'mobileToolbarOpen' | 'searchButtonColor' | 'searchInSidebar' +> & { + newButtonEnabled: boolean; + onSearchChange: (e: React.FormEvent) => void; + onSearchClear: (e: React.FormEvent) => void; + onSearchSubmit: (e: React.FormEvent) => void; + state: { + query: string; + formSubmitted: boolean; + }, + toggleMobile: (e: React.MouseEvent) => void; +}; + +function DesktopSearchInputWrapper({ + mobileToolbarOpen, + newButtonEnabled, + onSearchChange, + onSearchClear, + onSearchSubmit, + searchButtonColor, + searchInSidebar, + state, + toggleMobile, +}: CommonSearchInputParams) { + return ( + + + + {!state.formSubmitted && !newButtonEnabled && + + } + {state.formSubmitted && !newButtonEnabled && + + } + {state.formSubmitted && newButtonEnabled && + + + + } + {newButtonEnabled && + + } + + ); } -class Topbar extends React.Component { +function MobileSearchInputWrapper({ + mobileToolbarOpen, + newButtonEnabled, + onSearchChange, + onSearchClear, + onSearchSubmit, + searchButtonColor, + searchInSidebar, + state, + toggleMobile, + openSearchResults, + searchSidebarOpen, + hasSearchResults, + children, +} : React.PropsWithChildren< + CommonSearchInputParams & + Pick< + Props, + 'openSearchResults' | 'searchSidebarOpen' | 'hasSearchResults' + > +>) { + const openSearchbar = (e: React.MouseEvent) => { + e.preventDefault(); + openSearchResults(); + }; + const showBackToSearchResults = !searchSidebarOpen && hasSearchResults; - public static getDerivedStateFromProps(newProps: Props, state: State) { - if (newProps.query && newProps.query !== state.queryProp && newProps.query !== state.query) { - return { ...state, query: newProps.query, queryProp: newProps.query }; - } - return { ...state, queryProp: newProps.query }; - } + return ( + + + + {showBackToSearchResults && + + {(msg) => + {msg} + } + } + {!showBackToSearchResults && + + {(msg) => + {msg} + } + } + + + {!state.formSubmitted && !newButtonEnabled && + + } + { + state.query && newButtonEnabled && + + + } + { + state.query && !newButtonEnabled && + } + + {children} + + + + ); +} + +// Effectively a conditionally executed hook +function AltSCycler({hasSearchResults}: {hasSearchResults: boolean}) { + const isMobile = useMatchMobileQuery(); + const cycleSearchRegions = React.useCallback( + // Only in desktop layout + () => { + if (isMobile) { + return; + } + const targets = [ + '[class*="SearchInputWrapper"] input', + '[class*="SearchResultsBar"]', + 'main', + ].map((q) => document?.querySelector(q)); + + // Determine which region we are in (if any) + const currentSectionIndex = targets.findIndex((el) => el?.contains(document?.activeElement!)); + + // If not in any, go to search input + if (currentSectionIndex < 0) { + targets[0]?.focus(); + return; + } + // If there are no search results, toggle between search input and main content + if (!hasSearchResults) { + targets[currentSectionIndex === 0 ? 2 : 0]?.focus(); + return; + } + const nextSectionIndex = (currentSectionIndex + 1) % targets.length; - public state = { query: '', queryProp: '', formSubmitted: false }; + targets[nextSectionIndex]?.focus(); + // Possibly we want to scroll the current result into view in results or content? + }, + [isMobile, hasSearchResults] + ); - public render() { - const openMenu = (e: React.MouseEvent) => { + useKeyCombination(searchKeyCombination, cycleSearchRegions); + + return null; +} + +function Topbar(props: Props) { + const openMenu = React.useCallback( + (e: React.MouseEvent) => { e.preventDefault(); - this.props.openMobileMenu(); - }; + props.openMobileMenu(); + }, + [props] + ); + const newButtonEnabled = !!props.searchButtonColor; + const prevQuery = React.useRef(''); + const [query, setQuery] = React.useState(''); + const [formSubmitted, setFormSubmitted] = React.useState(false); + const state = React.useMemo( + () => ({query, formSubmitted}), + [query, formSubmitted] + ); + + if (props.query) { + if (props.query !== query && props.query !== prevQuery.current) { + setQuery(props.query); + } + prevQuery.current = props.query; + } - const onSearchSubmit = (e: React.FormEvent) => { + const onSearchChange = React.useCallback( + ({currentTarget}: React.FormEvent) => { + setQuery(currentTarget.value); + setFormSubmitted(false); + }, + [] + ); + const onSearchClear = React.useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + setQuery(''); + setFormSubmitted(false); + }, + [] + ); + const onSearchSubmit = React.useCallback( + (e: React.FormEvent) => { e.preventDefault(); const activeElement = assertDocument().activeElement; - if (this.state.query) { + if (query) { if (isHtmlElement(activeElement)) { activeElement.blur(); } - this.props.search(this.state.query); - this.setState({ formSubmitted: true }); + props.search(query); + setFormSubmitted(true); } - }; - - const onSearchClear = (e: React.FormEvent) => { - e.preventDefault(); - this.setState({ query: '', formSubmitted: false }); - }; - - const toggleMobile = (e: React.MouseEvent) => { + }, + [props, query] + ); + const toggleMobile = React.useCallback( + (e: React.MouseEvent) => { e.preventDefault(); - if (this.props.mobileToolbarOpen) { - this.props.clearSearch(); + if (props.mobileToolbarOpen) { + props.clearSearch(); } else { - this.props.openMobileToolbar(); + props.openMobileToolbar(); } - }; - - const openSearchbar = (e: React.MouseEvent) => { - e.preventDefault(); - this.props.openSearchResults(); - }; + }, + [props] + ); - const onSearchChange = (e: React.FormEvent) => { - this.setState({ query: (e.currentTarget as any).value, formSubmitted: false }); - }; - - const showBackToSearchResults = !this.props.searchSidebarOpen && this.props.hasSearchResults; - - const newButtonEnabled = !!this.props.searchButtonColor; - - return + return ( + + {typeof window !== 'undefined' && } - - - - {!this.state.formSubmitted && !newButtonEnabled && - - } - {this.state.formSubmitted && !newButtonEnabled && - - } - {this.state.formSubmitted && newButtonEnabled && - - - - } - {newButtonEnabled && - - } - + - - - - {showBackToSearchResults && - - {(msg) => - {msg} - } - } - {!showBackToSearchResults && - - {(msg) => - {msg} - } - } - - - {!this.state.formSubmitted && !newButtonEnabled && - - } - { - this.state.query && newButtonEnabled && - - - } - { - this.state.query && !newButtonEnabled && - } - - + - - - ; - } + + + ); } export default connect( @@ -232,6 +371,7 @@ export default connect( searchInSidebar: selectSearch.searchInSidebar(state), searchSidebarOpen: selectSearch.searchResultsOpen(state), textSize: selectContent.textSize(state), + selectedResult: selectSearch.selectedResult(state), }), (dispatch: Dispatch) => ({ clearSearch: flow(clearSearch, dispatch), diff --git a/src/app/content/highlights/constants.ts b/src/app/content/highlights/constants.ts index 9e0430a6bf..ab8b10bfcd 100644 --- a/src/app/content/highlights/constants.ts +++ b/src/app/content/highlights/constants.ts @@ -17,3 +17,8 @@ export const highlightKeyCombination: KeyCombinationOptions = { code: 'KeyH', // key isn't always h when alt is pressed altKey: true, }; + +export const searchKeyCombination: KeyCombinationOptions = { + code: 'KeyS', + altKey: true, +}; diff --git a/src/app/content/keyboardShortcuts/components/ShowKeyboardShortcuts.tsx b/src/app/content/keyboardShortcuts/components/ShowKeyboardShortcuts.tsx index 19f86d6ae3..27754a452d 100644 --- a/src/app/content/keyboardShortcuts/components/ShowKeyboardShortcuts.tsx +++ b/src/app/content/keyboardShortcuts/components/ShowKeyboardShortcuts.tsx @@ -152,6 +152,7 @@ const ShowKeyboardShortcuts = () => ( + diff --git a/src/app/content/keyboardShortcuts/components/__snapshots__/KeyboardShortcutsPopup.spec.tsx.snap b/src/app/content/keyboardShortcuts/components/__snapshots__/KeyboardShortcutsPopup.spec.tsx.snap index 3eb9960853..2ecdd9a87d 100644 --- a/src/app/content/keyboardShortcuts/components/__snapshots__/KeyboardShortcutsPopup.spec.tsx.snap +++ b/src/app/content/keyboardShortcuts/components/__snapshots__/KeyboardShortcutsPopup.spec.tsx.snap @@ -603,6 +603,30 @@ exports[`KeyboardShortcuts renders keyboard shortcuts modal if it is open 1`] = Create a highlight or note after selecting text
+
+
+ + Option (Mac) or Alt (other) + + + + + s + +
+
+ Navigate among search bar, search results, and main content region +
+
diff --git a/src/app/content/keyboardShortcuts/components/__snapshots__/ShowKeyboardShortcuts.spec.tsx.snap b/src/app/content/keyboardShortcuts/components/__snapshots__/ShowKeyboardShortcuts.spec.tsx.snap index dc5125cc25..3b43d6690e 100644 --- a/src/app/content/keyboardShortcuts/components/__snapshots__/ShowKeyboardShortcuts.spec.tsx.snap +++ b/src/app/content/keyboardShortcuts/components/__snapshots__/ShowKeyboardShortcuts.spec.tsx.snap @@ -375,6 +375,30 @@ exports[`ShowKeyboardShortcuts renders the keyboard shortcuts menu 1`] = ` Create a highlight or note after selecting text +
+
+ + Option (Mac) or Alt (other) + + + + + s + +
+
+ Navigate among search bar, search results, and main content region +
+
diff --git a/src/app/content/search/components/SearchResultsSidebar/RelatedKeyTerms.tsx b/src/app/content/search/components/SearchResultsSidebar/RelatedKeyTerms.tsx index 853eaba2a5..388505de9a 100644 --- a/src/app/content/search/components/SearchResultsSidebar/RelatedKeyTerms.tsx +++ b/src/app/content/search/components/SearchResultsSidebar/RelatedKeyTerms.tsx @@ -27,7 +27,11 @@ const RelatedKeyTerms = ({ book, keyTermHits, selectedResult }: { hits={keyTermHits} testId='related-key-term-result' getPage={(hit: SearchResultHit) => assertDefined(findArchiveTreeNodeById(book.tree, hit.source.pageId), 'hit has to be in a book')} - onClick={() => dispatch(closeSearchResultsMobile())} + onClick={() => { + dispatch(closeSearchResultsMobile()); + // Timeout may not be necessary after #2221 is merged + setTimeout(() => document?.querySelector('main')?.focus(), 20); + }} selectedResult={selectedResult} /> ; diff --git a/src/app/content/search/components/SearchResultsSidebar/SearchResultContainers.tsx b/src/app/content/search/components/SearchResultsSidebar/SearchResultContainers.tsx index c6164569d3..01024c86f9 100644 --- a/src/app/content/search/components/SearchResultsSidebar/SearchResultContainers.tsx +++ b/src/app/content/search/components/SearchResultsSidebar/SearchResultContainers.tsx @@ -66,6 +66,14 @@ const SearchResult = (props: { const formatMessage = useIntl().formatMessage; const active = props.page && props.currentPage && stripIdVersion(props.currentPage.id) === stripIdVersion(props.page.id); + const selectResultAndFocus = React.useCallback( + (result: SelectedResult) => { + props.selectResult(result); + // Timeout may not be necessary after #2221 is merged + setTimeout(() => document?.querySelector('main')?.focus(), 20); + }, + [props] + ); return props.page} - onClick={(result: SelectedResult) => props.selectResult(result)} + onClick={selectResultAndFocus} selectedResult={props.selectedResult} /> ; diff --git a/src/app/content/search/components/SearchResultsSidebar/SearchResultHits.tsx b/src/app/content/search/components/SearchResultsSidebar/SearchResultHits.tsx index 7aa03da8e2..eaa02b001a 100644 --- a/src/app/content/search/components/SearchResultsSidebar/SearchResultHits.tsx +++ b/src/app/content/search/components/SearchResultsSidebar/SearchResultHits.tsx @@ -82,6 +82,7 @@ const OneSearchResultHit = ({ return ( ( - (props, ref) => + (props, ref) => { + const forwardFocus = React.useCallback( + ({target, currentTarget}: FocusEvent) => { + if (target !== currentTarget) { + return; + } + const currentResult = (ref as React.MutableRefObject) + .current.querySelector('[aria-current="true"]'); + + currentResult?.focus(); + }, + [ref] + ); + + return ( + + ); + } ); export class SearchResultsBarWrapper extends Component { @@ -78,7 +97,7 @@ export class SearchResultsBarWrapper extends Component { public blankState = () => - + {(msg) => msg} @@ -103,7 +122,7 @@ export class SearchResultsBarWrapper extends Component { public totalResults = () => - + {(msg) => msg} @@ -164,7 +183,7 @@ export class SearchResultsBarWrapper extends Component { assertDefined(a.highlight.title, 'highlight should have title') .localeCompare(assertDefined(b.highlight.title, 'highlight should have title'))); - return + return {displayRelatedKeyTerms &&

Search results @@ -849,7 +854,8 @@ exports[`SearchResultsSidebar matches snapshot with related key terms 1`] = `

- + `; @@ -1463,6 +1472,8 @@ exports[`SearchResultsSidebar matches snapshot with results 1`] = ` className="c0" data-analytics-region="search-results" data-testid="search-results-sidebar" + onFocus={[Function]} + tabIndex={-1} >
Search results @@ -1538,7 +1550,8 @@ exports[`SearchResultsSidebar matches snapshot with results 1`] = `
-
- + `; @@ -1992,6 +2008,8 @@ exports[`SearchResultsSidebar shows sidebar with loading state if there is a sea className="c0" data-analytics-region="search-results" data-testid="search-results-sidebar" + onFocus={[Function]} + tabIndex={-1} >
{ let store: Store; @@ -172,6 +173,7 @@ describe('SearchResultsSidebar', () => { }); it('closes mobile search results when related key term is clicked', () => { + jest.useFakeTimers(); store.dispatch(receivePage({ ...pageInChapter, references: [] })); store.dispatch(requestSearch('term')); store.dispatch( @@ -197,6 +199,8 @@ describe('SearchResultsSidebar', () => { findById('related-key-term-result').props.onClick(makeEvent()); }); + jest.runAllTimers(); + expect(dispatch).toHaveBeenCalledWith(closeSearchResultsMobile()); }); @@ -214,6 +218,7 @@ describe('SearchResultsSidebar', () => { }); it('closes search results when one is clicked', () => { + jest.useFakeTimers(); store.dispatch(requestSearch('cool search')); store.dispatch( receiveSearchResults( @@ -229,6 +234,7 @@ describe('SearchResultsSidebar', () => { renderer.act(() => { findById('search-result').props.onClick(makeEvent()); }); + jest.runAllTimers(); expect(dispatch).toHaveBeenCalledWith(closeSearchResultsMobile()); }); @@ -253,7 +259,32 @@ describe('SearchResultsSidebar', () => { expect(dispatch).toHaveBeenCalledWith(clearSearch()); }); - it ('scrolls to search scroll target if result selected by user', () => { + // This is purely a code coverage test. + // It should have a selected result that receives focus when the bar is focused, + // and when the button is focused, it should keep it. + it('sidebar tries to forward focus to current search result', () => { + jest.spyOn(selectNavigation, 'persistentQueryParameters').mockReturnValue({query: 'cool search'}); + renderToDom(render()); + ReactTestUtils.act( + () => { + store.dispatch(receivePage({ ...pageInChapter, references: [] })); + store.dispatch(requestSearch('cool search')); + store.dispatch(receiveSearchResults(makeSearchResults([]))); + } + ); + + const document = assertDocument(); + const bar = document.querySelector('[class*="SearchResultsBar"]'); + const button = document.querySelector('button'); + + ReactTestUtils.act(() => bar?.focus()); + expect(document.activeElement).toBe(bar); + + ReactTestUtils.act(() => button?.focus()); + expect(document.activeElement).toBe(button); + }); + + it('scrolls to search scroll target if result selected by user', () => { const searchResult = makeSearchResultHit({ book: archiveBook, page }); const searchScrollTarget: SearchScrollTarget = { type: 'search', index: 0, elementId: 'elementId' }; const scrollSidebarSectionIntoView = jest.spyOn(domUtils, 'scrollSidebarSectionIntoView'); diff --git a/src/app/content/search/components/SearchResultsSidebar/styled.tsx b/src/app/content/search/components/SearchResultsSidebar/styled.tsx index cd9e75b742..aa718378c2 100644 --- a/src/app/content/search/components/SearchResultsSidebar/styled.tsx +++ b/src/app/content/search/components/SearchResultsSidebar/styled.tsx @@ -54,7 +54,7 @@ export const CloseIcon = styled((props) =>