import { beforeEach, describe, expect, it } from "vitest"; import type { PostInput } from "@/lib/services/posts"; import { createPost, getPublishedPostBySlug, listPublishedPosts, setPostStatus, } from "@/lib/services/posts"; import { resetDb } from "../helpers/db"; const input = (overrides: Partial = {}): PostInput => ({ title: "A Post", slug: "", body: "Some body text for the post.", authorName: "Tester", featuredImageUrl: null, featuredImageAlt: null, status: "draft", tagIds: [], newTagNames: [], ...overrides, }); beforeEach(resetDb); describe("public post queries", () => { it("excludes draft posts from listings and slug lookups", async () => { await createPost(input({ title: "Published one", status: "published" })); const draft = await createPost(input({ title: "Hidden draft", status: "draft" })); const page = await listPublishedPosts({ page: 1, perPage: 10 }); expect(page.total).toBe(1); expect(page.items.map((p) => p.title)).toEqual(["Published one"]); expect(await getPublishedPostBySlug(draft.slug)).toBeNull(); }); it("orders posts in reverse chronological order of publication", async () => { await createPost(input({ title: "Oldest", status: "published" })); await createPost(input({ title: "Middle", status: "published" })); await createPost(input({ title: "Newest", status: "published" })); const page = await listPublishedPosts({ page: 1, perPage: 10 }); expect(page.items.map((p) => p.title)).toEqual(["Newest", "Middle", "Oldest"]); }); it("paginates with correct totals", async () => { for (let i = 1; i <= 7; i++) { await createPost(input({ title: `Post ${i}`, status: "published" })); } const first = await listPublishedPosts({ page: 1, perPage: 5 }); expect(first.items).toHaveLength(5); expect(first.total).toBe(7); expect(first.pageCount).toBe(2); const second = await listPublishedPosts({ page: 2, perPage: 5 }); expect(second.items).toHaveLength(2); expect(second.items.map((p) => p.title)).toEqual(["Post 2", "Post 1"]); }); }); describe("creating and publishing a post", () => { it("keeps a new draft private, then makes it public on publish", async () => { const draft = await createPost(input({ title: "Launch notes" })); expect(draft.publishedAt).toBeNull(); expect(await getPublishedPostBySlug("launch-notes")).toBeNull(); const published = await setPostStatus(draft.id, "published"); expect(published?.status).toBe("published"); expect(published?.publishedAt).toBeInstanceOf(Date); const publicPost = await getPublishedPostBySlug("launch-notes"); expect(publicPost?.title).toBe("Launch notes"); const listing = await listPublishedPosts({ page: 1, perPage: 10 }); expect(listing.items.map((p) => p.slug)).toContain("launch-notes"); }); it("stamps publishedAt once and keeps it across unpublish/republish", async () => { const post = await createPost(input({ status: "published" })); const firstDate = post.publishedAt; expect(firstDate).toBeInstanceOf(Date); const unpublished = await setPostStatus(post.id, "draft"); expect(unpublished?.publishedAt?.getTime()).toBe(firstDate?.getTime()); const republished = await setPostStatus(post.id, "published"); expect(republished?.publishedAt?.getTime()).toBe(firstDate?.getTime()); }); it("attaches existing and newly created tags", async () => { const post = await createPost( input({ status: "published", newTagNames: ["Fresh Tag", "Another"] }), ); const publicPost = await getPublishedPostBySlug(post.slug); expect(publicPost?.tags.map((t) => t.slug).sort()).toEqual(["another", "fresh-tag"]); }); });