mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
release: v1.32.0 — hardening and scalability (#580)
* release: prepare v1.32.0 * test: stabilize water capture re-entry
This commit is contained in:
1 parent
b01822be48
commit
62eb431d66
372 files changed
+25178
-8561
No files matched your search
@@ -27,6 +27,17 @@ function render(node: React.ReactNode) {
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
function buttonMarkupByName(html: string, name: string): string {
|
||||
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const markup = html.match(
|
||||
new RegExp(
|
||||
`<button(?=[^>]*aria-label="${escapedName}")[^>]*>[\\s\\S]*?</button>`,
|
||||
),
|
||||
)?.[0];
|
||||
expect(markup).toBeDefined();
|
||||
return markup ?? "";
|
||||
}
|
||||
|
||||
const suggestion = (
|
||||
overrides: Partial<DocumentSuggestionDto> = {},
|
||||
): DocumentSuggestionDto => ({
|
||||
@@ -112,6 +123,27 @@ describe("<AssistSuggestionReview>", () => {
|
||||
);
|
||||
expect(html).toContain("Nothing could be read from this document");
|
||||
});
|
||||
|
||||
it("keeps dismiss compact while providing a 44px mobile target", () => {
|
||||
const html = render(
|
||||
<AssistSuggestionReview
|
||||
suggestion={suggestion()}
|
||||
kindLabel="Lab result"
|
||||
dateLabel="1 Mar 2026"
|
||||
applied={{ title: false, kind: false, date: false }}
|
||||
onUseTitle={noop}
|
||||
onUseKind={noop}
|
||||
onUseDate={noop}
|
||||
onDismiss={noop}
|
||||
/>,
|
||||
);
|
||||
const dismiss = buttonMarkupByName(html, "Dismiss");
|
||||
expect(dismiss).toContain("size-11");
|
||||
expect(dismiss).toContain("sm:size-7");
|
||||
expect(dismiss).toContain('aria-label="Dismiss"');
|
||||
expect(dismiss).toContain("<svg");
|
||||
expect(dismiss).toContain("size-4");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<DocumentSummaryPanel>", () => {
|
||||
@@ -170,4 +202,22 @@ describe("<DocumentSummaryPanel>", () => {
|
||||
expect(html).toContain('role="alert"');
|
||||
expect(html).toContain("read the document");
|
||||
});
|
||||
|
||||
it("keeps close compact while providing a 44px mobile target", () => {
|
||||
const html = render(
|
||||
<DocumentSummaryPanel
|
||||
output="summary"
|
||||
result={{ summary: "A lipid panel dated 1 March 2026." }}
|
||||
isPending={false}
|
||||
errorKey={null}
|
||||
onClose={noop}
|
||||
/>,
|
||||
);
|
||||
const close = buttonMarkupByName(html, "Hide");
|
||||
expect(close).toContain("size-11");
|
||||
expect(close).toContain("sm:size-7");
|
||||
expect(close).toContain('aria-label="Hide"');
|
||||
expect(close).toContain("<svg");
|
||||
expect(close).toContain("size-4");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
closeDocumentSelectionHistoryEntry,
|
||||
closeDocumentSelectionAfterCoachHandoff,
|
||||
documentSelectionHistoryState,
|
||||
documentSelectionHref,
|
||||
withoutDocumentSelectionHref,
|
||||
} from "../documents-view";
|
||||
|
||||
class MemoryHistory {
|
||||
private entries: { href: string; state: unknown }[];
|
||||
private index: number;
|
||||
|
||||
constructor(entries: string[]) {
|
||||
this.entries = entries.map((href) => ({ href, state: null }));
|
||||
this.index = entries.length - 1;
|
||||
}
|
||||
|
||||
get href() {
|
||||
return this.entries[this.index]?.href;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.entries[this.index]?.state;
|
||||
}
|
||||
|
||||
pushState(state: unknown, _unused: string, href: string | URL) {
|
||||
this.entries.splice(this.index + 1, Infinity, {
|
||||
href: href.toString(),
|
||||
state,
|
||||
});
|
||||
this.index += 1;
|
||||
}
|
||||
|
||||
replaceState(state: unknown, _unused: string, href?: string | URL | null) {
|
||||
this.entries[this.index] = {
|
||||
href: href?.toString() ?? this.href!,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
back() {
|
||||
if (this.index > 0) this.index -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
describe("document vault URL selection", () => {
|
||||
it("adds an ordinary card selection without dropping unrelated query state", () => {
|
||||
expect(
|
||||
documentSelectionHref(
|
||||
"/documents",
|
||||
"episode=episode-1&view=compact&q=MRT",
|
||||
"doc_123",
|
||||
),
|
||||
).toBe("/documents?episode=episode-1&view=compact&q=MRT&doc=doc_123");
|
||||
});
|
||||
|
||||
it("replaces an existing selection instead of appending a duplicate", () => {
|
||||
expect(
|
||||
documentSelectionHref(
|
||||
"/documents",
|
||||
"episode=episode-1&doc=old-document",
|
||||
"new-document",
|
||||
),
|
||||
).toBe("/documents?episode=episode-1&doc=new-document");
|
||||
});
|
||||
|
||||
it.each(["", "../coach", "doc?id=other", "doc id", "doc\n", "a".repeat(41)])(
|
||||
"refuses to write an unsafe document id: %j",
|
||||
(documentId) => {
|
||||
expect(
|
||||
documentSelectionHref(
|
||||
"/documents",
|
||||
"episode=episode-1&view=compact",
|
||||
documentId,
|
||||
),
|
||||
).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it("removes only the document selection on close", () => {
|
||||
expect(
|
||||
withoutDocumentSelectionHref(
|
||||
"/documents",
|
||||
"episode=episode-1&doc=doc_123&view=compact&q=MRT",
|
||||
),
|
||||
).toBe("/documents?episode=episode-1&view=compact&q=MRT");
|
||||
});
|
||||
|
||||
it("returns the pathname when doc was the only query parameter", () => {
|
||||
expect(withoutDocumentSelectionHref("/documents", "doc=doc_123")).toBe(
|
||||
"/documents",
|
||||
);
|
||||
});
|
||||
|
||||
it("consumes a card-open entry so the next Back reaches the prior distinct page", () => {
|
||||
const history = new MemoryHistory(["/today", "/documents?episode=ep-1"]);
|
||||
const openHref = documentSelectionHref(
|
||||
"/documents",
|
||||
"episode=ep-1",
|
||||
"doc-1",
|
||||
);
|
||||
expect(openHref).not.toBeNull();
|
||||
history.pushState(documentSelectionHistoryState("doc-1"), "", openHref!);
|
||||
|
||||
closeDocumentSelectionHistoryEntry(
|
||||
history,
|
||||
"/documents",
|
||||
"episode=ep-1&doc=doc-1",
|
||||
"doc-1",
|
||||
);
|
||||
|
||||
expect(history.href).toBe("/documents?episode=ep-1");
|
||||
history.back();
|
||||
expect(history.href).toBe("/today");
|
||||
});
|
||||
|
||||
it("consumes a dismissed Coach handoff and closes its card-owned detail entry", () => {
|
||||
const history = new MemoryHistory(["/today", "/documents?episode=ep-1"]);
|
||||
history.pushState(
|
||||
documentSelectionHistoryState("doc-1"),
|
||||
"",
|
||||
"/documents?episode=ep-1&doc=doc-1",
|
||||
);
|
||||
|
||||
const retainedHandoff = closeDocumentSelectionAfterCoachHandoff(
|
||||
history,
|
||||
"/documents",
|
||||
"episode=ep-1&doc=doc-1",
|
||||
"doc-1",
|
||||
"doc-1",
|
||||
"dismiss",
|
||||
);
|
||||
|
||||
expect(retainedHandoff).toBeNull();
|
||||
expect(history.href).toBe("/documents?episode=ep-1");
|
||||
});
|
||||
|
||||
it("retains a navigating Coach handoff for maximize and browser Back", () => {
|
||||
const history = new MemoryHistory(["/today", "/documents?episode=ep-1"]);
|
||||
history.pushState(
|
||||
documentSelectionHistoryState("doc-1"),
|
||||
"",
|
||||
"/documents?episode=ep-1&doc=doc-1",
|
||||
);
|
||||
|
||||
const retainedHandoff = closeDocumentSelectionAfterCoachHandoff(
|
||||
history,
|
||||
"/documents",
|
||||
"episode=ep-1&doc=doc-1",
|
||||
"doc-1",
|
||||
"doc-1",
|
||||
"navigate",
|
||||
);
|
||||
|
||||
expect(retainedHandoff).toBe("doc-1");
|
||||
expect(history.href).toBe("/documents?episode=ep-1&doc=doc-1");
|
||||
});
|
||||
|
||||
it("replaces a direct deep-link selection without navigating away", () => {
|
||||
const history = new MemoryHistory([
|
||||
"/today",
|
||||
"/documents?episode=ep-1&doc=doc-1",
|
||||
]);
|
||||
history.replaceState({ __NA: true }, "", history.href);
|
||||
closeDocumentSelectionHistoryEntry(
|
||||
history,
|
||||
"/documents",
|
||||
"episode=ep-1&doc=doc-1",
|
||||
"doc-1",
|
||||
);
|
||||
|
||||
expect(history.href).toBe("/documents?episode=ep-1");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user