mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
fix(labs): keep what is typed in the search field when the URL catches up
The labs search writes its trimmed query to the URL after a short pause, and every change of the URL query replaced the field's text. The field's own write came back as well: a trailing space vanished, so "vitamin " followed by "d" searched for "vitamind", and a letter typed while the write landed was lost. A query the field wrote itself now leaves the field alone; one from back/forward, a link or Reset still replaces it.
This commit is contained in:
1 parent
04a4353e50
commit
74464184c5
2 files changed
+56
-6
No files matched your search
@@ -289,6 +289,47 @@ describe("LabList stale category and group header handling", () => {
|
||||
expect(readStoredOpen("collapsed")).toEqual({});
|
||||
});
|
||||
|
||||
it("keeps a trailing space in the search field after the URL takes the query", async () => {
|
||||
setSearch("");
|
||||
const queryClient = labQueryClient();
|
||||
queryClient.setQueryData(
|
||||
queryKeys.labResultsOverview("en"),
|
||||
overviewWithAbnormalMarker,
|
||||
);
|
||||
|
||||
const { screen } = await renderWithApp(<LabList />, { queryClient });
|
||||
|
||||
const search = screen.getByRole("searchbox", { name: "Search biomarkers" });
|
||||
await search.fill("iron ");
|
||||
await expect.poll(currentSearch).toBe("?q=iron");
|
||||
// One more frame for the URL change to reach the hook.
|
||||
await new Promise((resolve) =>
|
||||
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
||||
);
|
||||
await expect.element(search).toHaveValue("iron ");
|
||||
});
|
||||
|
||||
it("takes the query from the URL on back navigation", async () => {
|
||||
setSearch("?q=iron");
|
||||
const queryClient = labQueryClient();
|
||||
queryClient.setQueryData(
|
||||
queryKeys.labResultsOverview("en"),
|
||||
overviewWithAbnormalMarker,
|
||||
);
|
||||
|
||||
const { screen } = await renderWithApp(<LabList />, { queryClient });
|
||||
const search = screen.getByRole("searchbox", { name: "Search biomarkers" });
|
||||
await expect.element(search).toHaveValue("iron");
|
||||
|
||||
await screen.getByRole("button", { name: "Reset" }).click();
|
||||
await expect.poll(currentSearch).toBe("");
|
||||
await expect.element(search).toHaveValue("");
|
||||
|
||||
window.history.back();
|
||||
await expect.poll(currentSearch).toBe("?q=iron");
|
||||
await expect.element(search).toHaveValue("iron");
|
||||
});
|
||||
|
||||
it("memoizes setCategoryFilter callback identity across re-renders", async () => {
|
||||
const callbacks: unknown[] = [];
|
||||
const { screen } = await renderWithApp(
|
||||
|
||||
@@ -19,20 +19,29 @@ export function useLabListFilters() {
|
||||
serialise: labListFiltersToSearch,
|
||||
});
|
||||
|
||||
// Debounced search draft
|
||||
const [searchDraft, setSearchDraft] = useState(filters.q ?? "");
|
||||
const [lastUrlQ, setLastUrlQ] = useState(filters.q ?? "");
|
||||
// The field edits a draft; the URL takes it, trimmed, 200 ms after the
|
||||
// last keystroke. A `q` the URL gains from elsewhere (back/forward, a
|
||||
// shared link, Reset) replaces the draft. One the draft wrote itself
|
||||
// does not: adopting it would drop a trailing space ("vitamin " became
|
||||
// "vitamin", and the next letter ran on), or a letter typed while the
|
||||
// write was landing.
|
||||
const urlQ = filters.q ?? "";
|
||||
const [searchDraft, setSearchDraft] = useState(urlQ);
|
||||
const [lastUrlQ, setLastUrlQ] = useState(urlQ);
|
||||
const [ownWrite, setOwnWrite] = useState<string | null>(null);
|
||||
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
if ((filters.q ?? "") !== lastUrlQ) {
|
||||
setLastUrlQ(filters.q ?? "");
|
||||
setSearchDraft(filters.q ?? "");
|
||||
if (urlQ !== lastUrlQ) {
|
||||
setLastUrlQ(urlQ);
|
||||
if (urlQ === ownWrite) setOwnWrite(null);
|
||||
else if (urlQ !== searchDraft.trim()) setSearchDraft(urlQ);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handle = setTimeout(() => {
|
||||
const trimmed = searchDraft.trim();
|
||||
if (trimmed === (filters.q ?? "")) return;
|
||||
setOwnWrite(trimmed);
|
||||
applyFilters(
|
||||
(current) =>
|
||||
trimmed === (current.q ?? "")
|
||||
|
||||
Reference in new issue
Block a user