mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
test(checkups): pin that a linked visit older than the list's newest hundred is read and opened
This commit is contained in:
1 parent
9a86a9427b
commit
3e2426187c
2 files changed
+143
-19
No files matched your search
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* `/checkups?visit=<id>` from a search hit. The visits list reads the newest
|
||||
* hundred of each arm, and a search reaches back further than that, so a hit
|
||||
* can name a visit the list does not hold. That visit is read on its own and
|
||||
* opened like a listed one.
|
||||
*/
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { openDeepLinked } from "../use-deep-linked-record";
|
||||
|
||||
type Visit = { id: string; occurredAt: string };
|
||||
|
||||
/** The newest hundred visits, the way the list holds them. */
|
||||
const newestHundred: Visit[] = Array.from({ length: 100 }, (_, i) => ({
|
||||
id: `v${i}`,
|
||||
occurredAt: `2026-01-01T00:00:00Z`,
|
||||
}));
|
||||
const older: Visit = { id: "v-older", occurredAt: "2014-05-06T09:00:00Z" };
|
||||
|
||||
describe("a link to a visit older than the newest hundred", () => {
|
||||
it("reads it on its own and opens it", async () => {
|
||||
const resolve = vi.fn(async (id: string) =>
|
||||
id === older.id ? older : null,
|
||||
);
|
||||
const open = vi.fn();
|
||||
const scrollTo = vi.fn();
|
||||
|
||||
const outcome = await openDeepLinked({
|
||||
wanted: older.id,
|
||||
records: newestHundred,
|
||||
open,
|
||||
resolve,
|
||||
stillWanted: () => true,
|
||||
scrollTo,
|
||||
});
|
||||
|
||||
expect(outcome).toBe("resolved");
|
||||
expect(resolve).toHaveBeenCalledWith("v-older");
|
||||
expect(open).toHaveBeenCalledWith(older);
|
||||
// Not on the page, so there is nothing to scroll to.
|
||||
expect(scrollTo).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens a listed visit without a second read", async () => {
|
||||
const resolve = vi.fn();
|
||||
const open = vi.fn();
|
||||
const scrollTo = vi.fn();
|
||||
const outcome = await openDeepLinked({
|
||||
wanted: "v42",
|
||||
records: newestHundred,
|
||||
open,
|
||||
resolve,
|
||||
stillWanted: () => true,
|
||||
scrollTo,
|
||||
});
|
||||
expect(outcome).toBe("listed");
|
||||
expect(resolve).not.toHaveBeenCalled();
|
||||
expect(scrollTo).toHaveBeenCalledWith("v42");
|
||||
expect(open).toHaveBeenCalledWith(newestHundred[42]);
|
||||
});
|
||||
|
||||
it("leaves the page alone when the visit is gone or refused", async () => {
|
||||
const open = vi.fn();
|
||||
for (const resolve of [
|
||||
async () => null,
|
||||
async () => {
|
||||
throw new Error("403");
|
||||
},
|
||||
]) {
|
||||
const outcome = await openDeepLinked({
|
||||
wanted: "v-gone",
|
||||
records: newestHundred,
|
||||
open,
|
||||
resolve,
|
||||
stillWanted: () => true,
|
||||
scrollTo: () => {},
|
||||
});
|
||||
expect(outcome).toBe("none");
|
||||
}
|
||||
expect(open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not open a late answer for a link that is no longer wanted", async () => {
|
||||
const open = vi.fn();
|
||||
const outcome = await openDeepLinked({
|
||||
wanted: older.id,
|
||||
records: newestHundred,
|
||||
open,
|
||||
resolve: async () => older,
|
||||
stillWanted: () => false,
|
||||
scrollTo: () => {},
|
||||
});
|
||||
expect(outcome).toBe("none");
|
||||
expect(open).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -56,24 +56,52 @@ export function useDeepLinkedRecord<T extends { id: string }>({
|
||||
useEffect(() => {
|
||||
if (!wanted || !records || handled.current === wanted) return;
|
||||
handled.current = wanted;
|
||||
const record = records.find((entry) => entry.id === wanted);
|
||||
if (!record) {
|
||||
if (!resolve || !open) return;
|
||||
void resolve(wanted)
|
||||
.then((found) => {
|
||||
// Still on the page, and still the link it was asked for.
|
||||
if (found && mounted.current && handled.current === wanted) {
|
||||
open(found);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Gone or refused: the page stays as it would without the link.
|
||||
});
|
||||
return;
|
||||
}
|
||||
document
|
||||
.querySelector(`[${anchorAttribute}="${CSS.escape(wanted)}"]`)
|
||||
?.scrollIntoView({ block: "center" });
|
||||
open?.(record);
|
||||
void openDeepLinked({
|
||||
wanted,
|
||||
records,
|
||||
open,
|
||||
resolve,
|
||||
// Still on the page, and still the link it was asked for.
|
||||
stillWanted: () => mounted.current && handled.current === wanted,
|
||||
scrollTo: (id) =>
|
||||
document
|
||||
.querySelector(`[${anchorAttribute}="${CSS.escape(id)}"]`)
|
||||
?.scrollIntoView({ block: "center" }),
|
||||
});
|
||||
}, [wanted, records, anchorAttribute, open, resolve]);
|
||||
}
|
||||
|
||||
/**
|
||||
* What a link does once the list has loaded, apart from React: a record the
|
||||
* list holds is scrolled to and opened; one it does not hold (older than the
|
||||
* bounded read, say the newest hundred visits) is read on its own through
|
||||
* `resolve` and opened when it comes back and the link is still wanted.
|
||||
* Resolves to which of the three happened.
|
||||
*/
|
||||
export async function openDeepLinked<T extends { id: string }>(args: {
|
||||
wanted: string;
|
||||
records: readonly T[];
|
||||
open?: (record: T) => void;
|
||||
resolve?: (id: string) => Promise<T | null>;
|
||||
stillWanted: () => boolean;
|
||||
scrollTo: (id: string) => void;
|
||||
}): Promise<"listed" | "resolved" | "none"> {
|
||||
const { wanted, records, open, resolve } = args;
|
||||
const record = records.find((entry) => entry.id === wanted);
|
||||
if (record) {
|
||||
args.scrollTo(wanted);
|
||||
open?.(record);
|
||||
return "listed";
|
||||
}
|
||||
if (!resolve || !open) return "none";
|
||||
let found: T | null;
|
||||
try {
|
||||
found = await resolve(wanted);
|
||||
} catch {
|
||||
// Gone or refused: the page stays as it would without the link.
|
||||
return "none";
|
||||
}
|
||||
if (!found || !args.stillWanted()) return "none";
|
||||
open(found);
|
||||
return "resolved";
|
||||
}
|
||||
Reference in new issue
Block a user