From 07f23c599033a62dc769f0b2a8d538d5bc6aa704 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 31 Aug 2020 16:06:31 -0700 Subject: [PATCH] feat: pressing / or s focuses search input (#1855) --- src/routes/_actions/goToSearch.js | 14 +++++++ src/routes/_components/NavShortcuts.html | 4 +- src/routes/_components/search/Search.html | 20 ++++++++-- tests/spec/040-shortcuts-search.js | 48 +++++++++++++++++++++++ tests/utils.js | 1 + 5 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 src/routes/_actions/goToSearch.js create mode 100644 tests/spec/040-shortcuts-search.js diff --git a/src/routes/_actions/goToSearch.js b/src/routes/_actions/goToSearch.js new file mode 100644 index 00000000..264ac4fc --- /dev/null +++ b/src/routes/_actions/goToSearch.js @@ -0,0 +1,14 @@ +import { store } from '../_store/store' +import { goto } from '../../../__sapper__/client' +import { emit } from '../_utils/eventBus' + +// Go to the search page, and also focus the search input. For accessibility +// and usability reasons, this only happens on pressing these particular hotkeys. +export async function goToSearch () { + if (store.get().currentPage === 'search') { + emit('focusSearchInput') + } else { + store.set({ focusSearchInput: true }) + goto('/search') + } +} diff --git a/src/routes/_components/NavShortcuts.html b/src/routes/_components/NavShortcuts.html index f31d9c37..b2192f72 100644 --- a/src/routes/_components/NavShortcuts.html +++ b/src/routes/_components/NavShortcuts.html @@ -5,7 +5,7 @@ - + {#if !$leftRightChangesFocus} @@ -23,6 +23,7 @@ import { importShowComposeDialog } from './dialog/asyncDialogs/importShowComposeDialog' import { store } from '../_store/store' import { normalizePageName } from '../_utils/normalizePageName' + import { goToSearch } from '../_actions/goToSearch' export default { store: () => store, @@ -34,6 +35,7 @@ console.log('nav shortcuts') }, goto, + goToSearch, async showShortcutHelpDialog () { const showShortcutHelpDialog = await importShowShortcutHelpDialog() showShortcutHelpDialog() diff --git a/src/routes/_components/search/Search.html b/src/routes/_components/search/Search.html index c93d292f..1e90c2a8 100644 --- a/src/routes/_components/search/Search.html +++ b/src/routes/_components/search/Search.html @@ -1,6 +1,7 @@
- this.focusSearchInput()) // user typed hotkey on this page itself + if (this.store.get().focusSearchInput) { // we arrived here from a goto via the search hotkey + this.store.set({ focusSearchInput: false }) // reset + this.focusSearchInput() + } + }, store: () => store, components: { LoadingPage, @@ -70,9 +80,13 @@ SvgIcon }, methods: { - async onSubmit (e) { + onSubmit (e) { e.preventDefault() - doSearch() + e.stopPropagation() + /* no await */ doSearch() + }, + focusSearchInput () { + tryToFocusElement('the-search-input') } } } diff --git a/tests/spec/040-shortcuts-search.js b/tests/spec/040-shortcuts-search.js new file mode 100644 index 00000000..d937e676 --- /dev/null +++ b/tests/spec/040-shortcuts-search.js @@ -0,0 +1,48 @@ +import { + getActiveElementTagName, + getNthStatus, + getUrl, + searchButton, searchInput, searchNavButton +} from '../utils' +import { loginAsFoobar } from '../roles' +import { Selector as $ } from 'testcafe' + +fixture`040-shortcuts-search.js` + .page`http://localhost:4002` + +test('Pressing / goes to search and focuses input but does not prevent left/right hotkeys afterwards', async t => { + await loginAsFoobar(t) + await t + .expect(getNthStatus(1).exists).ok() + .pressKey('/') + .expect(getUrl()).contains('/search') + .expect(getActiveElementTagName()).match(/input/i) + .typeText(searchInput, 'foo', { paste: true }) + .click(searchButton) // unfocus from the input + .expect(getActiveElementTagName()).notMatch(/input/i) + .pressKey('right') + .expect(getUrl()).contains('/settings') + .pressKey('left') + .expect(getUrl()).contains('/search') + // search input is not autofocused if we didn't arrive via the search hotkeys + .expect(getActiveElementTagName()).notMatch(/input/i) +}) + +test('Pressing / focuses the search input if we are already on the search page', async t => { + await loginAsFoobar(t) + await t + .click(searchNavButton) + .expect(getUrl()).contains('/search') + .expect(getActiveElementTagName()).notMatch(/input/i) + .pressKey('/') + .expect(getActiveElementTagName()).match(/input/i) +}) + +test('Pressing / without logging in just goes to the search page', async t => { + await t + .expect(getUrl()).eql('http://localhost:4002/') + .expect($('.main-content h1').innerText).eql('Pinafore') + .pressKey('/') + .expect(getUrl()).contains('/search') + .expect(getActiveElementTagName()).notMatch(/input/i) +}) diff --git a/tests/utils.js b/tests/utils.js index 8522ef61..91914b7e 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -33,6 +33,7 @@ export const logInToInstanceLink = $('a[href="/settings/instances/add"]') export const copyPasteModeButton = $('.copy-paste-mode-button') export const oauthCodeInput = $('#oauthCodeInput') export const searchInput = $('.search-input') +export const searchButton = $('button[aria-label=Search]') export const postStatusButton = $('.compose-box-button') export const showMoreButton = $('.more-items-header button') export const accountProfileName = $('.account-profile .account-profile-name')