From 3b3d82ce1314086e1a210c5e32fa4e663b95b57d Mon Sep 17 00:00:00 2001 From: "Mr. Meowgi" Date: Wed, 20 May 2026 19:15:01 -0300 Subject: [PATCH] refactor(docs): enhance frontmatter handling in DocPage (#2448) Replace compile-time-only 'as string' casts with runtime String() conversions and instanceof Date check. gray-matter parses unquoted YAML dates as Date objects, causing 'Objects are not valid as a React child' crashes on docs pages. Co-authored-by: Mr. Meowgi --- src/app/docs/[slug]/page.tsx | 10 ++++-- tests/unit/docs-site-overhaul.test.ts | 47 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/app/docs/[slug]/page.tsx b/src/app/docs/[slug]/page.tsx index d70daa0217..d081267e35 100644 --- a/src/app/docs/[slug]/page.tsx +++ b/src/app/docs/[slug]/page.tsx @@ -190,9 +190,13 @@ export default async function DocPage({ params }: { params: Promise<{ slug: stri } const fileContent = fs.readFileSync(sourceAbs, "utf8"); const { content, data: frontmatter } = matter(fileContent); - pageTitle = (frontmatter.title as string) || item.title; - version = (frontmatter.version as string) || null; - lastUpdated = (frontmatter.lastUpdated as string) || null; + pageTitle = String(frontmatter.title || item.title); + version = frontmatter.version ? String(frontmatter.version) : null; + lastUpdated = frontmatter.lastUpdated + ? frontmatter.lastUpdated instanceof Date + ? frontmatter.lastUpdated.toISOString().slice(0, 10) + : String(frontmatter.lastUpdated) + : null; mermaidCharts = extractMermaidCharts(content); headings = extractHeadings(content); htmlContent = renderMarkdown(content); diff --git a/tests/unit/docs-site-overhaul.test.ts b/tests/unit/docs-site-overhaul.test.ts index 584c828268..bc551f7bc5 100644 --- a/tests/unit/docs-site-overhaul.test.ts +++ b/tests/unit/docs-site-overhaul.test.ts @@ -258,6 +258,53 @@ test("SEARCH_INDEX entries have non-empty content", () => { } }); +// ────────────────────────────────────────────── +// Frontmatter type coercion (gray-matter parses +// unquoted YAML dates as Date, numbers as Number) +// ────────────────────────────────────────────── + +test("gray-matter parses unquoted YAML date as Date object", async () => { + const matter = (await import("gray-matter")).default; + const { data } = matter("---\nlastUpdated: 2026-05-13\n---\nBody"); + assert.ok(data.lastUpdated instanceof Date, "unquoted YAML date should be a Date instance"); +}); + +test("gray-matter keeps semver-like version as string", async () => { + const matter = (await import("gray-matter")).default; + const { data } = matter("---\nversion: 3.8.0\n---\nBody"); + assert.equal(typeof data.version, "string", "3.8.0 stays a string (two dots = not a number)"); +}); + +test("gray-matter parses single-dot version as number", async () => { + const matter = (await import("gray-matter")).default; + const { data } = matter("---\nversion: 3.8\n---\nBody"); + assert.equal(typeof data.version, "number", "3.8 is parsed as a float"); +}); + +test("frontmatter Date coercion produces YYYY-MM-DD string", () => { + const d = new Date("2026-05-13T00:00:00.000Z"); + const result = d instanceof Date ? d.toISOString().slice(0, 10) : String(d); + assert.equal(result, "2026-05-13"); +}); + +test("frontmatter String() coercion handles number version", () => { + const version = 3.8; + const result = version ? String(version) : null; + assert.equal(result, "3.8"); + assert.equal(typeof result, "string"); +}); + +test("frontmatter falsy values fall back correctly", () => { + const title = String(undefined || "Fallback Title"); + assert.equal(title, "Fallback Title"); + + const emptyTitle = String("" || "Fallback Title"); + assert.equal(emptyTitle, "Fallback Title"); + + const version = null ? String(null) : null; + assert.equal(version, null); +}); + // ────────────────────────────────────────────── // Mermaid extraction // ──────────────────────────────────────────────