From 7d48b7ea78f5b2758179c7677f3408bf8bd2c8f5 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 21 Aug 2026 13:56:28 -0700
Subject: [PATCH] Fix QL Copilot thing
---
src/components/quicklaunch.jsx | 25 ++++++-------
src/components/quicklaunch.test.jsx | 55 +++++++++++++++++++++++++----
2 files changed, 61 insertions(+), 19 deletions(-)
diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx
index 220c6dbb2..96df9fb24 100644
--- a/src/components/quicklaunch.jsx
+++ b/src/components/quicklaunch.jsx
@@ -15,6 +15,16 @@ const MOBILE_BUTTON_POSITIONS = {
"bottom-right": "bottom-4 right-4",
};
+function parseUrl(searchString) {
+ if (!/.+[.:].+/.test(searchString)) return null; // basic test for probably a url
+
+ try {
+ return new URL(searchString.toLowerCase().startsWith("http") ? searchString : `https://${searchString}`);
+ } catch {
+ return null;
+ }
+}
+
function getSearchResults({
hideVisitURL,
searchDescriptions,
@@ -77,7 +87,7 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
const searchField = useRef();
const [currentItemIndex, setCurrentItemIndex] = useState(null);
- const [url, setUrl] = useState(null);
+ const url = parseUrl(searchString);
const [searchSuggestions, setSearchSuggestions] = useState([]);
const { data: widgets } = useSWR("/api/widgets");
@@ -134,17 +144,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
function handleSearchChange(event) {
const rawSearchString = event.target.value;
setCurrentItemIndex(null);
- try {
- if (!/.+[.:].+/g.test(rawSearchString)) throw new Error(); // basic test for probably a url
- let urlString = rawSearchString;
- if (urlString.toLowerCase().indexOf("http") !== 0) urlString = `https://${rawSearchString}`;
- setUrl(new URL(urlString)); // basic validation
- setSearchString(rawSearchString);
- return;
- } catch (e) {
- setUrl(null);
- }
- setSearchString(rawSearchString.toLowerCase());
+ // urls keep their casing, everything else is lowercased for matching
+ setSearchString(parseUrl(rawSearchString) ? rawSearchString : rawSearchString.toLowerCase());
}
function handleSearchKeyDown(event) {
diff --git a/src/components/quicklaunch.test.jsx b/src/components/quicklaunch.test.jsx
index 65cf7a508..031471916 100644
--- a/src/components/quicklaunch.test.jsx
+++ b/src/components/quicklaunch.test.jsx
@@ -46,13 +46,25 @@ function Wrapper({ servicesAndBookmarks = [], initialOpen = true } = {}) {
const [isOpen, setSearching] = useState(initialOpen);
return (
-
+ <>
+
+
+ >
);
}
@@ -151,6 +163,35 @@ describe("components/quicklaunch", () => {
openSpy.mockRestore();
});
+ it("does not carry a previous url result into a search seeded by a keypress", async () => {
+ renderWithProviders(, {
+ settings: {
+ target: "_self",
+ quicklaunch: {
+ provider: "duckduckgo",
+ showSearchSuggestions: false,
+ },
+ },
+ });
+
+ const input = screen.getByPlaceholderText("Search");
+ await waitFor(() => expect(input).toHaveFocus());
+
+ fireEvent.change(input, { target: { value: "example.com" } });
+ expect(await screen.findByText("quicklaunch.visit URL")).toBeInTheDocument();
+
+ fireEvent.keyDown(input, { key: "Escape" });
+ await act(async () => {
+ await new Promise((r) => setTimeout(r, 350));
+ });
+
+ // reopen by "typing" a character, the way pages/index does
+ fireEvent.click(screen.getByTestId("seed-key"));
+
+ expect(input).toHaveValue("g");
+ expect(screen.queryByText("quicklaunch.visit URL")).not.toBeInTheDocument();
+ });
+
it("closes on Escape and clears the search string after the timeout", async () => {
renderWithProviders(, {
settings: {