yap-blog/tests/unit/slug.test.ts
2026-07-02 21:32:33 -04:00

61 lines
1.9 KiB
TypeScript

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");
});
});