import { describe, expect, it } from "vitest"; import { DEFAULT_AUTHOR, comparePosts, formatAuthors, formatPostDate, getPost, parseAuthors, postsFor, } from "../src/lib/blog"; const entry = (pinned: boolean, date: string, slug: string) => ({ pinned, date, slug }); describe("comparePosts", () => { it("sorts pinned first, then date desc, then slug", () => { const sorted = [ entry(false, "2026-07-20", "beta"), entry(false, "2026-07-21", "newest"), entry(true, "2026-07-01", "pinned-old"), entry(false, "2026-07-20", "alpha"), ].sort(comparePosts); expect(sorted.map((p) => p.slug)).toEqual(["pinned-old", "newest", "alpha", "beta"]); }); it("orders two pinned posts by date desc", () => { const sorted = [entry(true, "2026-07-01", "old"), entry(true, "2026-07-20", "new")].sort( comparePosts, ); expect(sorted.map((p) => p.slug)).toEqual(["new", "old"]); }); }); describe("formatPostDate", () => { it("formats en dates as long US dates", () => { expect(formatPostDate("2026-06-18", "en")).toBe("June 18, 2026"); expect(formatPostDate("2026-07-20", "en")).toBe("July 20, 2026"); }); it("formats zh dates as year-month-day", () => { expect(formatPostDate("2026-06-18", "zh")).toBe("2026年6月18日"); expect(formatPostDate("2026-07-20", "zh")).toBe("2026年7月20日"); }); it("interprets the date in UTC so the calendar day never shifts", () => { // 2026-01-01 rendered in a western timezone would show Dec 31 without UTC pinning. expect(formatPostDate("2026-01-01", "en")).toBe("January 1, 2026"); }); it("falls back to the raw string for unexpected input", () => { expect(formatPostDate("", "en")).toBe(""); expect(formatPostDate("not-a-date", "zh")).toBe("not-a-date"); }); it("falls back to the raw string for impossible calendar dates", () => { expect(formatPostDate("2026-02-31", "en")).toBe("2026-02-31"); expect(formatPostDate("2026-13-01", "en")).toBe("2026-13-01"); expect(formatPostDate("2026-04-00", "zh")).toBe("2026-04-00"); }); }); describe("parseAuthors / formatAuthors", () => { it("splits comma-class separators and trims", () => { expect(parseAuthors("A (AMD), B(X)、C ,D")).toEqual(["A (AMD)", "B(X)", "C", "D"]); }); it("falls back to the default byline when absent or blank", () => { expect(parseAuthors(undefined)).toEqual([DEFAULT_AUTHOR]); expect(parseAuthors(" ")).toEqual([DEFAULT_AUTHOR]); }); it("joins with a comma in English and an ideographic comma in Chinese", () => { expect(formatAuthors(["A", "B"], "en")).toBe("A, B"); expect(formatAuthors(["A", "B"], "zh")).toBe("A、B"); expect(formatAuthors(["A"], "en")).toBe("A"); }); }); describe("frontmatter mapping (author / pinned / category)", () => { it("defaults authors and pinned when the frontmatter omits them", () => { const post = getPost("july-2026-updates", "en"); expect(post?.authors).toEqual([DEFAULT_AUTHOR]); expect(post?.pinned).toBe(false); }); it("reads the explicit author list and the practice category", () => { const post = getPost("local-agents-on-amd-gpus", "en"); expect(post?.authors).toEqual([ "Ning Zhang (AMD)", "Yuyang Gao (AMD)", "Yaowei Zheng (PrismShadow)", ]); expect(post?.category).toBe("practice"); expect(post?.pinned).toBe(false); }); it("reads the pinned flag and sorts the pinned post first", () => { for (const locale of ["en", "zh"] as const) { const posts = postsFor(locale); expect(posts.length).toBe(13); // The launch post stays the single pinned post; newer posts sort under it by date. expect(posts.filter((p) => p.pinned).map((p) => p.slug)).toEqual([ "introducing-penguinharness", ]); expect(posts[0]?.slug).toBe("introducing-penguinharness"); // Pinning beats recency: the runner-up is strictly newer than the pinned post. Asserted // as a relation rather than a slug, because the newest date is shared by several posts // and the slug tie-break makes any single winner churn whenever a post is added. expect(posts[1]!.date > posts[0]!.date).toBe(true); } }); it("filters by the practice category, newest first", () => { expect(postsFor("en", "practice").map((p) => p.slug)).toEqual([ "natural-language-training-loop", "penguin-harness-self-improvement-with-amd-gpu", "local-agents-on-amd-gpus", ]); }); it("filters by the news category, newest first", () => { expect(postsFor("en", "news").map((p) => p.slug)).toEqual([ "introducing-penguinharness", "penguinharness-0-1-5", "penguinharness-0-1-4", "free-models-in-penguin-harness", "gemini-3-6-in-penguinharness", "fireworks-credits-amd", ]); }); it("filters by the perspectives category", () => { expect(postsFor("en", "perspectives").map((p) => p.slug)).toEqual([ // All three share 2026-07-22, so slug ascending is the tie-break. "ai-infrastructure-past-present-future", "easiest-way-to-build-ai-agents-2026", "simple-harness-is-all-you-need", ]); }); });