mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
test(links): prove a period search returns exactly that period
Parser cases for every accepted form in de, en, es, fr, it, pl and ko and the near misses that stay text; builder cases for every family; and an integration walk over three years of lab results, conditions, visits and documents, with records either side of each boundary in the person's zone, across every page.
This commit is contained in:
1 parent
e16c9fb717
commit
20a1ac4f7d
7 files changed
+743
-13
No files matched your search
@@ -18,6 +18,7 @@ vi.mock("@/components/links/use-link-picker-pages", async (importOriginal) => ({
|
||||
setTerm: vi.fn(),
|
||||
searchPending: false,
|
||||
filterLocally: true,
|
||||
period: null,
|
||||
hasOlder: false,
|
||||
hasNewer: false,
|
||||
loadingOlder: false,
|
||||
|
||||
@@ -125,6 +125,41 @@ describe("paged link picker", () => {
|
||||
expect(html).toContain('data-slot="labs-add"');
|
||||
});
|
||||
|
||||
it("names the period a date term searches, and does not filter its answer by the digits", () => {
|
||||
const html = render(
|
||||
<EntityLinkPicker
|
||||
{...base}
|
||||
options={loaded}
|
||||
selected={[]}
|
||||
server={server({
|
||||
term: "03.2019",
|
||||
filterLocally: true,
|
||||
period: {
|
||||
year: 2019,
|
||||
month: 3,
|
||||
fromDay: "2019-03-01",
|
||||
toDay: "2019-03-31",
|
||||
},
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(html).toContain('data-slot="labs-period"');
|
||||
expect(html).toContain("Showing March 2019");
|
||||
expect(html).toContain('data-slot="labs-option"');
|
||||
});
|
||||
|
||||
it("shows no period line for a plain term", () => {
|
||||
const html = render(
|
||||
<EntityLinkPicker
|
||||
{...base}
|
||||
options={loaded}
|
||||
selected={[]}
|
||||
server={server({ term: "metabolic" })}
|
||||
/>,
|
||||
);
|
||||
expect(html).not.toContain('data-slot="labs-period"');
|
||||
});
|
||||
|
||||
it("does not filter a server answer again by the term", () => {
|
||||
// The route matched on the panel name; the option does not render it.
|
||||
const html = render(
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* A picker term that names a year or a month becomes a date window; anything
|
||||
* else stays a text search. Every accepted form is checked in every UI
|
||||
* locale, and the near misses that must stay text are checked too.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { formatPeriod, parsePeriodQuery } from "../period-query";
|
||||
|
||||
const NOW = new Date("2026-10-15T12:00:00.000Z");
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const;
|
||||
|
||||
function window(term: string, locale: string) {
|
||||
const parsed = parsePeriodQuery(term, locale, NOW);
|
||||
return parsed ? `${parsed.fromDay}..${parsed.toDay}` : null;
|
||||
}
|
||||
|
||||
const MARCH = "2019-03-01..2019-03-31";
|
||||
const YEAR = "2019-01-01..2019-12-31";
|
||||
|
||||
describe("parsePeriodQuery", () => {
|
||||
describe.each(LOCALES)("%s", (locale) => {
|
||||
it("reads a year", () => {
|
||||
expect(window("2019", locale)).toBe(YEAR);
|
||||
expect(window(" 2019 ", locale)).toBe(YEAR);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"03.2019",
|
||||
"3.2019",
|
||||
"03/2019",
|
||||
"3/2019",
|
||||
"03-2019",
|
||||
"2019-03",
|
||||
"2019/03",
|
||||
"2019.03",
|
||||
"2019.3",
|
||||
" 03 . 2019 ",
|
||||
])("reads the numeric month %j", (term) => {
|
||||
expect(window(term, locale)).toBe(MARCH);
|
||||
});
|
||||
|
||||
it.each(["LDL", "2019abc", "13.2019", "00.2019", "1850", "1899", "2028"])(
|
||||
"leaves %j as text",
|
||||
(term) => {
|
||||
expect(window(term, locale)).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["", " ", "2019 2020", "03.2019.04", "LDL 2019", "3 2019 x"])(
|
||||
"leaves %j as text",
|
||||
(term) => {
|
||||
expect(window(term, locale)).toBeNull();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["de", ["März 2019", "märz 2019", "MÄRZ 2019", "Maerz 2019", "Marz 2019"]],
|
||||
["de", ["mär 2019", "Mär. 2019", "2019 März", "März, 2019"]],
|
||||
["en", ["March 2019", "march 2019", "Mar 2019", "Mar. 2019", "2019 March"]],
|
||||
["es", ["marzo 2019", "Marzo de 2019", "mar 2019", "mar. 2019"]],
|
||||
["fr", ["mars 2019", "Mars 2019", "mar 2019", "2019 mars"]],
|
||||
["it", ["marzo 2019", "Marzo 2019", "mar 2019", "marzo del 2019"]],
|
||||
["pl", ["marzec 2019", "Marzec 2019", "marca 2019", "mar 2019"]],
|
||||
["ko", ["2019년 3월", "2019년3월", "2019년 03월", "3월 2019", "2019. 3."]],
|
||||
] as const)("reads March 2019 in %s", (locale, terms) => {
|
||||
for (const term of terms) {
|
||||
expect(window(term, locale), term).toBe(MARCH);
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["de", "Dezember 2019", "2019-12-01..2019-12-31"],
|
||||
["de", "Okt 2019", "2019-10-01..2019-10-31"],
|
||||
["de", "Februar 2020", "2020-02-01..2020-02-29"],
|
||||
["en", "Sept 2019", "2019-09-01..2019-09-30"],
|
||||
["en", "Sep 2019", "2019-09-01..2019-09-30"],
|
||||
["en", "May 2019", "2019-05-01..2019-05-31"],
|
||||
["es", "septiembre 2019", "2019-09-01..2019-09-30"],
|
||||
["es", "ene 2019", "2019-01-01..2019-01-31"],
|
||||
["es", "dic 2019", "2019-12-01..2019-12-31"],
|
||||
["fr", "février 2019", "2019-02-01..2019-02-28"],
|
||||
["fr", "fevrier 2019", "2019-02-01..2019-02-28"],
|
||||
["fr", "févr. 2019", "2019-02-01..2019-02-28"],
|
||||
["fr", "août 2019", "2019-08-01..2019-08-31"],
|
||||
["fr", "déc 2019", "2019-12-01..2019-12-31"],
|
||||
["fr", "juil. 2019", "2019-07-01..2019-07-31"],
|
||||
["it", "gennaio 2019", "2019-01-01..2019-01-31"],
|
||||
["it", "giu 2019", "2019-06-01..2019-06-30"],
|
||||
["it", "set 2019", "2019-09-01..2019-09-30"],
|
||||
["pl", "październik 2019", "2019-10-01..2019-10-31"],
|
||||
["pl", "paź 2019", "2019-10-01..2019-10-31"],
|
||||
["pl", "października 2019", "2019-10-01..2019-10-31"],
|
||||
["pl", "luty 2019", "2019-02-01..2019-02-28"],
|
||||
["pl", "styczeń 2019", "2019-01-01..2019-01-31"],
|
||||
["ko", "2019년 12월", "2019-12-01..2019-12-31"],
|
||||
["ko", "2019년", YEAR],
|
||||
// Austrian German calls January "Jänner", and Intl knows it.
|
||||
["de-AT", "Jänner 2019", "2019-01-01..2019-01-31"],
|
||||
["de-AT", "März 2019", MARCH],
|
||||
] as const)("%s %j", (locale, term, expected) => {
|
||||
expect(window(term, locale)).toBe(expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
// "jui" is both juin and juillet; "ma" is too short to name one month.
|
||||
["fr", "jui 2019"],
|
||||
["en", "ma 2019"],
|
||||
["en", "Ju 2019"],
|
||||
// A month name from another language is text in this one.
|
||||
["en", "März 2019"],
|
||||
["de", "March 2019"],
|
||||
// A month without a year is not a period.
|
||||
["en", "March"],
|
||||
["de", "03"],
|
||||
// Two months, or a month name with digits glued on.
|
||||
["en", "March May"],
|
||||
["en", "March2019"],
|
||||
] as const)("%s %j stays text", (locale, term) => {
|
||||
expect(window(term, locale)).toBeNull();
|
||||
});
|
||||
|
||||
it("accepts next year and refuses the one after", () => {
|
||||
expect(window("2027", "en")).toBe("2027-01-01..2027-12-31");
|
||||
expect(window("12.2027", "en")).toBe("2027-12-01..2027-12-31");
|
||||
expect(window("2028", "en")).toBeNull();
|
||||
expect(window("1900", "en")).toBe("1900-01-01..1900-12-31");
|
||||
});
|
||||
|
||||
it("names the year and the month", () => {
|
||||
expect(parsePeriodQuery("2019", "de", NOW)).toEqual({
|
||||
year: 2019,
|
||||
month: null,
|
||||
fromDay: "2019-01-01",
|
||||
toDay: "2019-12-31",
|
||||
});
|
||||
expect(parsePeriodQuery("Maerz 2019", "de", NOW)).toMatchObject({
|
||||
year: 2019,
|
||||
month: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatPeriod", () => {
|
||||
it("names a period the way the locale writes it", () => {
|
||||
const march = parsePeriodQuery("03.2019", "de", NOW)!;
|
||||
expect(formatPeriod(march, "de")).toBe("März 2019");
|
||||
expect(formatPeriod(march, "en")).toBe("March 2019");
|
||||
expect(formatPeriod(march, "ko")).toBe("2019년 3월");
|
||||
expect(formatPeriod(parsePeriodQuery("2019", "en", NOW)!, "en")).toBe(
|
||||
"2019",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -28,15 +28,24 @@ import {
|
||||
} from "../use-link-picker-pages";
|
||||
|
||||
const pivot = pickerPivot("2019-05-10T10:00:00.000Z");
|
||||
const ctx = { locale: "en", timeZone: "UTC" } as const;
|
||||
|
||||
const REQUESTS: PickerPageRequest[] = [
|
||||
{ direction: "older", pivot, term: "", cursor: null },
|
||||
{ direction: "older", pivot, term: "", cursor: "200" },
|
||||
{ direction: "older", pivot: null, term: "", cursor: null },
|
||||
{ direction: "newer", pivot, term: "", cursor: null },
|
||||
{ direction: "newer", pivot, term: "", cursor: "100" },
|
||||
{ direction: "search", pivot: null, term: "ferritin", cursor: null },
|
||||
{ direction: "search", pivot: null, term: "ferritin", cursor: "100" },
|
||||
{ direction: "older", pivot, term: "", cursor: null, ...ctx },
|
||||
{ direction: "older", pivot, term: "", cursor: "200", ...ctx },
|
||||
{ direction: "older", pivot: null, term: "", cursor: null, ...ctx },
|
||||
{ direction: "newer", pivot, term: "", cursor: null, ...ctx },
|
||||
{ direction: "newer", pivot, term: "", cursor: "100", ...ctx },
|
||||
{ direction: "search", pivot: null, term: "ferritin", cursor: null, ...ctx },
|
||||
{ direction: "search", pivot: null, term: "ferritin", cursor: "100", ...ctx },
|
||||
{ direction: "search", pivot: null, term: "2019", cursor: null, ...ctx },
|
||||
{
|
||||
direction: "search",
|
||||
pivot: null,
|
||||
term: "March 2019",
|
||||
cursor: "100",
|
||||
...ctx,
|
||||
},
|
||||
];
|
||||
|
||||
function params(url: string): Record<string, string> {
|
||||
@@ -67,10 +76,22 @@ describe("documents picker", () => {
|
||||
|
||||
it("splits the day-granular window at the pivot's day", () => {
|
||||
const older = params(
|
||||
documentPickerUrl({ direction: "older", pivot, term: "", cursor: null }),
|
||||
documentPickerUrl({
|
||||
direction: "older",
|
||||
pivot,
|
||||
term: "",
|
||||
cursor: null,
|
||||
...ctx,
|
||||
}),
|
||||
);
|
||||
const newer = params(
|
||||
documentPickerUrl({ direction: "newer", pivot, term: "", cursor: null }),
|
||||
documentPickerUrl({
|
||||
direction: "newer",
|
||||
pivot,
|
||||
term: "",
|
||||
cursor: null,
|
||||
...ctx,
|
||||
}),
|
||||
);
|
||||
expect(older.to).toBe("2019-05-17");
|
||||
expect(newer.from).toBe("2019-05-18");
|
||||
@@ -88,10 +109,22 @@ describe("the windows around a record's date", () => {
|
||||
|
||||
it("older reads up to the split newest first, newer after it oldest first", () => {
|
||||
const older = params(
|
||||
labPickerUrl({ direction: "older", pivot, term: "", cursor: null }),
|
||||
labPickerUrl({
|
||||
direction: "older",
|
||||
pivot,
|
||||
term: "",
|
||||
cursor: null,
|
||||
...ctx,
|
||||
}),
|
||||
);
|
||||
const newer = params(
|
||||
labPickerUrl({ direction: "newer", pivot, term: "", cursor: null }),
|
||||
labPickerUrl({
|
||||
direction: "newer",
|
||||
pivot,
|
||||
term: "",
|
||||
cursor: null,
|
||||
...ctx,
|
||||
}),
|
||||
);
|
||||
expect(older).toMatchObject({ to: pivot, sortDir: "desc", offset: "0" });
|
||||
expect(newer).toMatchObject({
|
||||
@@ -107,6 +140,7 @@ describe("the windows around a record's date", () => {
|
||||
pivot: null,
|
||||
term: "ferritin",
|
||||
cursor: null,
|
||||
...ctx,
|
||||
}),
|
||||
);
|
||||
expect(search.q).toBe("ferritin");
|
||||
@@ -120,3 +154,88 @@ describe("the windows around a record's date", () => {
|
||||
expect(nextOffset("100", 3)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("a search term that names a period", () => {
|
||||
const search = (
|
||||
term: string,
|
||||
locale = "en",
|
||||
timeZone = "UTC",
|
||||
): PickerPageRequest => ({
|
||||
direction: "search",
|
||||
pivot: null,
|
||||
term,
|
||||
cursor: null,
|
||||
locale,
|
||||
timeZone,
|
||||
});
|
||||
|
||||
it.each([
|
||||
["labs", labPickerUrl, "sortDir"],
|
||||
["episodes", episodePickerUrl, "sortDir"],
|
||||
] as const)(
|
||||
"%s: a year is the year's instant window, newest first, with no q",
|
||||
(_name, build, sortKey) => {
|
||||
const sent = params(build(search("2019")));
|
||||
expect(sent.q).toBeUndefined();
|
||||
expect(sent.from).toBe("2019-01-01T00:00:00.000Z");
|
||||
expect(sent.to).toBe("2019-12-31T23:59:59.999Z");
|
||||
expect(sent[sortKey]).toBe("desc");
|
||||
},
|
||||
);
|
||||
|
||||
it("labs: a month in the person's language is that month", () => {
|
||||
const sent = params(labPickerUrl(search("März 2019", "de")));
|
||||
expect(sent.q).toBeUndefined();
|
||||
expect(sent.from).toBe("2019-03-01T00:00:00.000Z");
|
||||
expect(sent.to).toBe("2019-03-31T23:59:59.999Z");
|
||||
});
|
||||
|
||||
it("the window follows the person's timezone, DST included", () => {
|
||||
// Berlin: March 2019 starts at UTC+1 and ends at UTC+2.
|
||||
const sent = params(
|
||||
episodePickerUrl(search("03.2019", "de", "Europe/Berlin")),
|
||||
);
|
||||
expect(sent.from).toBe("2019-02-28T23:00:00.000Z");
|
||||
expect(sent.to).toBe("2019-03-31T21:59:59.999Z");
|
||||
});
|
||||
|
||||
it("documents: the day-granular window is the period's two calendar days", () => {
|
||||
const sent = params(documentPickerUrl(search("2019-02")));
|
||||
expect(sent.q).toBeUndefined();
|
||||
expect(sent.from).toBe("2019-02-01");
|
||||
expect(sent.to).toBe("2019-02-28");
|
||||
expect(sent.order).toBe("desc");
|
||||
});
|
||||
|
||||
it("encounters: the route without text search still gets the period", () => {
|
||||
const sent = params(encounterPickerUrl(search("Mar 2019")));
|
||||
expect(sent.q).toBeUndefined();
|
||||
expect(sent.from).toBe("2019-03-01T00:00:00.000Z");
|
||||
expect(sent.to).toBe("2019-03-31T23:59:59.999Z");
|
||||
});
|
||||
|
||||
it.each([labPickerUrl, episodePickerUrl, documentPickerUrl])(
|
||||
"a plain term stays a text search (%#)",
|
||||
(build) => {
|
||||
for (const term of ["LDL", "2019abc", "13.2019", "1850"]) {
|
||||
const sent = params(build(search(term)));
|
||||
expect(sent.q).toBe(term);
|
||||
expect(sent.from).toBeUndefined();
|
||||
expect(sent.to).toBeUndefined();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("a month name is read in the UI locale only", () => {
|
||||
// "März" is not an English month, so it stays text.
|
||||
expect(params(labPickerUrl(search("März 2019", "en"))).q).toBe("März 2019");
|
||||
});
|
||||
|
||||
it("the older and newer windows never read the term", () => {
|
||||
const older = params(
|
||||
labPickerUrl({ ...search("2019"), direction: "older", pivot }),
|
||||
);
|
||||
expect(older.to).toBe(pivot);
|
||||
expect(older.from).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -113,6 +113,8 @@ describe("fetchVaultPage", () => {
|
||||
pivot: "2026-05-17T23:59:59.999Z",
|
||||
term: "",
|
||||
cursor: "z",
|
||||
locale: "en",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
expect(page.items.map((d) => d.id)).toEqual(["a"]);
|
||||
expect(page.next).toBe("a");
|
||||
|
||||
Reference in new issue
Block a user