Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alt-S to navigate between search regions #2189

Merged
merged 60 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d3999d9
Make search results a nav, labelled by its title
RoyEJohnson Mar 6, 2024
8985db1
When search result is selected, focus on current search result
RoyEJohnson Mar 6, 2024
31759b8
Convert TopBar to function
RoyEJohnson Mar 6, 2024
7a791ca
Alt-S navigates between search input, results, and main content
RoyEJohnson Mar 7, 2024
c56cac1
Tests/code coverage
RoyEJohnson May 22, 2024
d2d7c82
Work if window is undefined
RoyEJohnson May 22, 2024
01466a4
Update tests
RoyEJohnson May 23, 2024
8a116fd
Cleanup casts
RoyEJohnson Jun 27, 2024
d953bc1
Add keys:s to messages files
RoyEJohnson Jun 28, 2024
5223294
Fix navigation
RoyEJohnson Jul 29, 2024
59e4222
Do key terms, too.
RoyEJohnson Jul 30, 2024
dd2d665
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
9c70e4e
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
4ad4119
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
d08d42b
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
b0e2863
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
43d399a
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
85e35a8
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
b3e1b11
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
e053362
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
5f04c89
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
4ee72ca
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
827e178
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
8a04d55
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
a7b8ee4
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
83114df
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
f746f82
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
a1a07dc
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
99a600d
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
4952312
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
4f45d58
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
3c6c740
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
48a7a1b
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
1c5d5cd
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
96e5c65
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
1804bc9
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
7bbf8ac
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
1e33401
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
4e6c3b8
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
044f8c7
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
9994261
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
933c44b
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
b012885
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
4ae1b78
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
57458ad
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
bfea474
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
77efbd8
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
6d5c457
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
7b1ff01
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
d2ab012
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
31359d1
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
526c3e4
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
5c21146
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
0716cde
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
894033e
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
7c878fd
Merge branch 'main' into search-navigation-issues
staxly[bot] Jul 31, 2024
5c507ea
Merge branch 'main' into search-navigation-issues
staxly[bot] Aug 8, 2024
2ba1c07
Merge branch 'main' into search-navigation-issues
staxly[bot] Aug 8, 2024
6584651
Merge branch 'main' into search-navigation-issues
staxly[bot] Aug 8, 2024
0e8a5de
Merge branch 'main' into search-navigation-issues
staxly[bot] Aug 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/app/content/components/Page.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() => {
Expand Down Expand Up @@ -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() => {
Expand Down
6 changes: 2 additions & 4 deletions src/app/content/components/Page/searchHighlightManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this scrollTo() just gone now? I noticed it was gone from the tests too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. focus will scroll an element into view unless you specify that it shouldn't.

container?.focus();
focusTarget?.focus();
}
);
}
Expand Down
97 changes: 91 additions & 6 deletions src/app/content/components/Topbar/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
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';
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,
Expand All @@ -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;
Expand All @@ -43,13 +59,17 @@ describe('search', () => {
};
});

const render = () => renderer.create(<Provider store={store}>
const render = (options?: TestRendererOptions) => renderer.create(<Provider store={store}>
<Services.Provider value={services}>
<MessageProvider>
<Topbar />
</MessageProvider>
</Services.Provider>
</Provider>);
</Provider>, options);

const dispatchSearchShortcut = (target: HTMLElement | undefined) => {
dispatchKeyDownEvent({code: searchKeyCombination.code, altKey: searchKeyCombination.altKey, target});
};

it('opens and closes mobile interface', () => {
const component = render();
Expand All @@ -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(
<Provider store={store}>
<Services.Provider value={services}>
<MessageProvider>
<Topbar />
<main tabIndex={-1} />
</MessageProvider>
</Services.Provider>
</Provider>
);
const tb = node.querySelector<HTMLElement>('[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(
<Provider store={store}>
<Services.Provider value={services}>
<MessageProvider>
<Topbar />
<div className='SearchResultsBar' tabIndex={-1} />
<main tabIndex={-1} />
</MessageProvider>
</Services.Provider>
</Provider>
);
const tb = node.querySelector<HTMLElement>('[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(
<Provider store={store}>
<Services.Provider value={services}>
<MessageProvider>
<Topbar />
<main tabIndex={-1} />
</MessageProvider>
</Services.Provider>
</Provider>
);
const tb = node.querySelector<HTMLElement>('[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', () => {
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down
Loading
Loading