import { describe, expect, it } from "vitest"; import { ensureUniqueSlug, slugify } from "@/lib/slug"; describe("slugify", () => { it("lowercases and hyphenates plain titles", () => { expect(slugify("Hello, World!")).toBe("hello-world"); }); it("collapses runs of spaces and punctuation into single hyphens", () => { expect(slugify(" Multiple spaces &&& symbols!! ")).toBe("multiple-spaces-symbols"); }); it("strips diacritics", () => { expect(slugify("Crème Brûlée à Paris")).toBe("creme-brulee-a-paris"); }); it("drops emoji and other non-latin symbols", () => { expect(slugify("🎉 Party time 🎉")).toBe("party-time"); }); it("keeps digits", () => { expect(slugify("Top 10 Things")).toBe("top-10-things"); }); it("returns an empty string when nothing survives", () => { expect(slugify("!!! ***")).toBe(""); }); it("truncates very long titles without a trailing hyphen", () => { const slug = slugify(`${"word ".repeat(40)}end`); expect(slug.length).toBeLessThanOrEqual(96); expect(slug.endsWith("-")).toBe(false); }); }); describe("ensureUniqueSlug", () => { const takenSet = (...taken: string[]) => { const set = new Set(taken); return async (slug: string) => set.has(slug); }; it("returns the base slug when free", async () => { expect(await ensureUniqueSlug("my-post", takenSet())).toBe("my-post"); }); it("appends -2 on the first conflict", async () => { expect(await ensureUniqueSlug("my-post", takenSet("my-post"))).toBe("my-post-2"); }); it("keeps counting past multiple conflicts", async () => { expect( await ensureUniqueSlug("my-post", takenSet("my-post", "my-post-2", "my-post-3")), ).toBe("my-post-4"); }); it("falls back to 'untitled' for an empty base", async () => { expect(await ensureUniqueSlug("", takenSet())).toBe("untitled"); expect(await ensureUniqueSlug("", takenSet("untitled"))).toBe("untitled-2"); }); });