Skip to content

Commit 1218453

Browse files
committed
feat: update tests and fix handling of selected item
Selected item was jumping when scrolling up because of focus. Hook to the up/down arrows on a document level instead of section level.
1 parent 69d1ee8 commit 1218453

3 files changed

Lines changed: 24 additions & 35 deletions

File tree

app/components/AppHeader.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ onKeyStroke(',', e => {
9595
<div class="flex-1 flex items-center justify-center gap-4 sm:gap-6">
9696
<!-- Search bar (shown on all pages except home and search) -->
9797
<search v-if="showSearchBar" class="hidden sm:block flex-1 max-w-md">
98-
<form method="GET" action="/search" class="relative" @submit.prevent="handleSearchInput">
98+
<form method="GET" action="/search" class="relative">
9999
<label for="header-search" class="sr-only">
100100
{{ $t('search.label') }}
101101
</label>

app/pages/search.vue

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { formatNumber } from '#imports'
33
import type { FilterChip, SortOption } from '#shared/types/preferences'
4+
import { onKeyDown } from '@vueuse/core'
45
import { debounce } from 'perfect-debounce'
56
import { isValidNewPackageName, checkPackageExists } from '~/utils/package-name'
67
import { isPlatformSpecificPackage } from '~/utils/platform-packages'
@@ -638,50 +639,22 @@ function scrollToSelectedItem() {
638639
}
639640
}
640641

641-
function focusSelectedItem() {
642-
const suggIdx = toSuggestionIndex(unifiedSelectedIndex.value)
643-
const pkgIdx = toPackageIndex(unifiedSelectedIndex.value)
644-
645-
nextTick(() => {
646-
if (suggIdx !== null) {
647-
const el = document.querySelector<HTMLElement>(`[data-suggestion-index="${suggIdx}"]`)
648-
el?.focus()
649-
} else if (pkgIdx !== null) {
650-
scrollToSelectedItem()
651-
nextTick(() => {
652-
const el = document.querySelector<HTMLElement>(`[data-result-index="${pkgIdx}"]`)
653-
el?.focus()
654-
})
655-
}
656-
})
657-
}
658-
659642
function handleResultsKeydown(e: KeyboardEvent) {
660643
if (totalSelectableCount.value <= 0) return
661644

662-
const isFromInput = (e.target as HTMLElement).tagName === 'INPUT'
663-
664645
if (e.key === 'ArrowDown') {
665646
e.preventDefault()
666647
userHasNavigated.value = true
667648
unifiedSelectedIndex.value = clampUnifiedIndex(unifiedSelectedIndex.value + 1)
668-
if (isFromInput) {
669-
scrollToSelectedItem()
670-
} else {
671-
focusSelectedItem()
672-
}
649+
scrollToSelectedItem()
673650
return
674651
}
675652

676653
if (e.key === 'ArrowUp') {
677654
e.preventDefault()
678655
userHasNavigated.value = true
679656
unifiedSelectedIndex.value = clampUnifiedIndex(unifiedSelectedIndex.value - 1)
680-
if (isFromInput) {
681-
scrollToSelectedItem()
682-
} else {
683-
focusSelectedItem()
684-
}
657+
scrollToSelectedItem()
685658
return
686659
}
687660

@@ -707,6 +680,8 @@ function handleResultsKeydown(e: KeyboardEvent) {
707680
}
708681
}
709682

683+
onKeyDown(['ArrowDown', 'ArrowUp', 'Enter'], handleResultsKeydown)
684+
710685
function handleSuggestionSelect(index: number) {
711686
// Convert suggestion index to unified index
712687
unifiedSelectedIndex.value = -(suggestionCount.value - index)
@@ -731,7 +706,7 @@ defineOgImageComponent('Default', {
731706
<main class="overflow-x-hidden">
732707
<!-- Results area with container padding -->
733708
<div class="container-sm py-6">
734-
<section v-if="query" :aria-label="$t('search.results')" @keydown="handleResultsKeydown">
709+
<section v-if="query" :aria-label="$t('search.results')">
735710
<!-- Initial loading (only after user interaction, not during view transition) -->
736711
<LoadingSpinner v-if="showSearching" :text="$t('search.searching')" />
737712

tests/interactions.spec.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ test.describe('Search Pages', () => {
66

77
await expect(page.locator('text=/found \\d+/i')).toBeVisible()
88

9-
const searchInput = page.locator('input[type="search"]')
10-
await expect(searchInput).toBeFocused()
11-
129
const firstResult = page.locator('[data-result-index="0"]').first()
1310
await expect(firstResult).toBeVisible()
1411

12+
const searchInput = page.locator('input[type="search"]')
13+
// manually focus search
14+
await searchInput.focus()
15+
1516
// ArrowDown changes visual selection but keeps focus in input
1617
await page.keyboard.press('ArrowDown')
1718
await expect(searchInput).toBeFocused()
@@ -35,4 +36,17 @@ test.describe('Search Pages', () => {
3536
await page.keyboard.press('/')
3637
await expect(page.locator('input[type="search"]')).toBeFocused()
3738
})
39+
40+
test('/settings → search, keeps focus on search input', async ({ page, goto }) => {
41+
await goto('/settings', { waitUntil: 'domcontentloaded' })
42+
43+
const searchInput = page.locator('input[type="search"]')
44+
await searchInput.fill('vue')
45+
46+
await page.waitForLoadState('domcontentloaded')
47+
48+
await expect(page.locator('text=/found \\d+/i')).toBeVisible()
49+
50+
await expect(searchInput).toBeFocused()
51+
})
3852
})

0 commit comments

Comments
 (0)