mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
Merge branch 'fix/v1437-review' into release/v1.43.7
# Conflicts: # src/app/api/analytics/route.ts
This commit is contained in:
commit
45c027aee2
217 files changed
+3636
-2127
No files matched your search
@@ -35,6 +35,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
vi.mock("@/lib/db", () => ({
|
||||
prisma: {
|
||||
measurement: { findMany: vi.fn() },
|
||||
// The empty recovery page reads the Oura fields for its reason.
|
||||
user: { findUnique: vi.fn().mockResolvedValue(null) },
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { walkSourceFiles } from "@/__tests__/helpers/source-files";
|
||||
|
||||
/**
|
||||
* No raw control byte in a source file, apart from tab, newline and carriage
|
||||
* return.
|
||||
*
|
||||
* A control byte typed into a string literal is invisible in an editor, a
|
||||
* diff and a review, renders as nothing or as a box, and survives copy and
|
||||
* paste unseen; the same value written as an escape (`"\x01"`) is the same
|
||||
* string and can be read. Three such bytes had crept in, one of them in a
|
||||
* production join key.
|
||||
*/
|
||||
const CONTROL = /[\x00-\x08\x0b\x0c\x0e-\x1f]/;
|
||||
|
||||
describe("source files carry no raw control bytes", () => {
|
||||
it("every file under src/ is free of them", () => {
|
||||
const src = join(process.cwd(), "src");
|
||||
const files = walkSourceFiles(src, {
|
||||
floor: 3000,
|
||||
extensions: [".ts", ".tsx", ".js", ".mjs", ".css", ".json", ".md"],
|
||||
}).filter((file) => !file.startsWith("generated/"));
|
||||
// A sweep that read nothing would agree with any tree.
|
||||
expect(files.length).toBeGreaterThan(3000);
|
||||
const offenders: string[] = [];
|
||||
for (const file of files) {
|
||||
const lines = readFileSync(join(src, file), "utf8").split("\n");
|
||||
lines.forEach((line, i) => {
|
||||
if (CONTROL.test(line)) offenders.push(`${file}:${i + 1}`);
|
||||
});
|
||||
}
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("recognises a control byte when one is there", () => {
|
||||
expect(CONTROL.test("a\u0001b")).toBe(true);
|
||||
expect(CONTROL.test("tab\tnewline\ncr\r")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Every read-failure primitive is handed the error it reports.
|
||||
*
|
||||
* `QueryErrorRow` and `QueryErrorCard` decide what a failed read offers from
|
||||
* the error itself: nothing (or the surface's refused state) for a 403, a
|
||||
* note without a retry for a 429, sign-in for a 401, a retry otherwise. A
|
||||
* call site that renders one without the error falls back to "failed" for
|
||||
* every status, which is how a refused share showed an error card and a spent
|
||||
* budget offered a retry that could only be refused again.
|
||||
*
|
||||
* The prop is required in the type, so the compiler already catches a missing
|
||||
* one. What it cannot catch is a literal that defeats it (`error={null}`,
|
||||
* `error={undefined}`), so this guard reads every opening tag under `src/`
|
||||
* and refuses both. The matcher is whitespace-tolerant (tags span lines) and
|
||||
* the match count is asserted, so a matcher that stops finding the tags fails
|
||||
* rather than passing over nothing.
|
||||
*/
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join, relative } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const SRC = join(__dirname, "..");
|
||||
|
||||
function walk(dir: string, out: string[] = []): string[] {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
if (entry.name === "__tests__" || entry.name === "generated") continue;
|
||||
const path = join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(path, out);
|
||||
else if (entry.name.endsWith(".tsx") && !entry.name.includes(".test."))
|
||||
out.push(path);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The opening tag's attribute text, from the name to its closing `>`. */
|
||||
function openingTags(source: string): string[] {
|
||||
const tags: string[] = [];
|
||||
const open = /<(QueryErrorRow|QueryErrorCard)(?=[\s/>])/g;
|
||||
for (let m = open.exec(source); m; m = open.exec(source)) {
|
||||
let depth = 0;
|
||||
let i = m.index + m[0].length;
|
||||
for (; i < source.length; i++) {
|
||||
const ch = source[i];
|
||||
if (ch === "{") depth++;
|
||||
else if (ch === "}") depth--;
|
||||
else if (ch === ">" && depth === 0) break;
|
||||
}
|
||||
tags.push(source.slice(m.index, i + 1));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/** Comments mention the primitives by name (`<QueryErrorCard>`); skip them. */
|
||||
function stripComments(source: string): string {
|
||||
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
||||
}
|
||||
|
||||
const sites = walk(SRC).flatMap((file) =>
|
||||
openingTags(stripComments(readFileSync(file, "utf8"))).map((tag) => ({
|
||||
file: relative(SRC, file),
|
||||
tag,
|
||||
})),
|
||||
);
|
||||
|
||||
describe("read-failure primitives carry their error", () => {
|
||||
it("finds the call sites it guards", () => {
|
||||
// About 120 at the time of writing. A drop to a handful means the
|
||||
// matcher broke, not that the sites went away.
|
||||
expect(sites.length).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
it("passes the query's error at every site", () => {
|
||||
const offenders = sites
|
||||
.filter(
|
||||
({ tag }) =>
|
||||
!/\serror=\{/.test(tag) ||
|
||||
/\serror=\{\s*(null|undefined)\s*\}/.test(tag),
|
||||
)
|
||||
.map(({ file, tag }) => `${file}: ${tag.split("\n")[0]}`);
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -216,6 +216,78 @@ describe("date formatters name the calendar they render in", () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A component renders dates through `useFormatters()`, never through the
|
||||
* shared cache directly.
|
||||
*
|
||||
* The two-argument `getDateTimeFormat(locale, options)` from
|
||||
* `@/lib/intl/formatter-cache` can name a zone and still be wrong: it takes
|
||||
* the locale and the hour cycle from wherever the caller found them and
|
||||
* knows nothing of the reader's date-order preference. The workout surfaces
|
||||
* rebuilt their own formatters this way and showed "Dec 16" to a reader who
|
||||
* had chosen YYYY-MM-DD. The three-argument `(locale, timeZone, options)`
|
||||
* form from `@/lib/tz/intl-cache` builds day keys and clock parts, not
|
||||
* display text, and stays allowed.
|
||||
*/
|
||||
describe("components format dates through useFormatters", () => {
|
||||
const CALL = /\bgetDateTimeFormat\s*\(/g;
|
||||
|
||||
function scan() {
|
||||
const files = execFileSync(
|
||||
"grep",
|
||||
[
|
||||
"-rlE",
|
||||
"getDateTimeFormat\\s*\\(",
|
||||
"src/components",
|
||||
"--include=*.ts",
|
||||
"--include=*.tsx",
|
||||
],
|
||||
{ cwd: REPO_ROOT, encoding: "utf8" },
|
||||
)
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.filter((f) => !f.includes("__tests__"));
|
||||
const offenders: string[] = [];
|
||||
let calls = 0;
|
||||
for (const file of files) {
|
||||
const source = readFileSync(resolve(REPO_ROOT, file), "utf8");
|
||||
CALL.lastIndex = 0;
|
||||
for (let m = CALL.exec(source); m; m = CALL.exec(source)) {
|
||||
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
||||
if (/^\s*(?:\/\/|\*|\/\*)/.test(source.slice(lineStart, m.index)))
|
||||
continue;
|
||||
calls += 1;
|
||||
const args = argumentList(source, m.index + m[0].length - 1);
|
||||
if (topLevelArgCount(args) < 3) {
|
||||
offenders.push(
|
||||
`${file}:${source.slice(0, m.index).split("\n").length}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { offenders, calls };
|
||||
}
|
||||
|
||||
it("still sees the cache's calls in components (the matcher matches)", () => {
|
||||
expect(scan().calls).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("has no two-argument getDateTimeFormat in src/components", () => {
|
||||
expect(scan().offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("the matcher flags the two-argument form across lines", () => {
|
||||
const source =
|
||||
'getDateTimeFormat(\n locale,\n { timeZone, month: "short" },\n)';
|
||||
CALL.lastIndex = 0;
|
||||
const m = CALL.exec(source)!;
|
||||
expect(
|
||||
topLevelArgCount(argumentList(source, m.index + m[0].length - 1)),
|
||||
).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A stated calendar date must not go through a zone-aware short formatter.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user