diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index 7375259ce..f6e12c430 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -124,8 +124,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea ? MOBILE_BUTTON_POSITIONS[settings.quicklaunch.mobileButtonPosition] : null; - function openCurrentItem(newWindow) { - const result = results[activeItemIndex]; + function openCurrentItem(newWindow, index = activeItemIndex) { + const result = results[index]; window.open( result.href, newWindow ? "_blank" : (result.target ?? searchProvider?.target ?? settings.target ?? "_blank"), @@ -180,7 +180,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea function handleItemClick(event) { closeAndReset(); - openCurrentItem(event.metaKey); + // in case hover doesnt fire, use the clicked item, not the highlighted one + openCurrentItem(event.metaKey, parseInt(event.currentTarget.dataset.index, 10)); } function handleItemKeyDown(event) { diff --git a/src/components/quicklaunch.test.jsx b/src/components/quicklaunch.test.jsx index 031471916..02bba7ac1 100644 --- a/src/components/quicklaunch.test.jsx +++ b/src/components/quicklaunch.test.jsx @@ -263,6 +263,37 @@ describe("components/quicklaunch", () => { openSpy.mockRestore(); }); + it("opens the clicked result even without a preceding hover event", async () => { + const openSpy = vi.spyOn(window, "open").mockImplementation(() => null); + + renderWithProviders( + , + { settings: { target: "_self", quicklaunch: { showSearchSuggestions: false } } }, + ); + + const input = screen.getByPlaceholderText("Search"); + await waitFor(() => expect(input).toHaveFocus()); + + fireEvent.change(input, { target: { value: "al" } }); + await waitFor(() => expect(document.querySelector('button[data-index="2"]')).toBeTruthy()); + + // touch devices don't fire mouseEnter, so the highlighted item is still the first one + fireEvent.click(document.querySelector('button[data-index="2"]')); + + await act(async () => { + await new Promise((r) => setTimeout(r, 350)); + }); + + expect(openSpy).toHaveBeenCalledWith("https://almond.example", "_self", "noreferrer"); + openSpy.mockRestore(); + }); + it("handles Escape on a result button (not just the input)", async () => { renderWithProviders(, { settings: { quicklaunch: { showSearchSuggestions: false } },