84 lines
3 KiB
TypeScript
84 lines
3 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { SlugConflictError } from "@/lib/services/errors";
|
|
import type { PostInput } from "@/lib/services/posts";
|
|
import { createPost, updatePost } from "@/lib/services/posts";
|
|
import { createPage } from "@/lib/services/pages";
|
|
import { resetDb } from "../helpers/db";
|
|
|
|
const input = (overrides: Partial<PostInput> = {}): PostInput => ({
|
|
title: "My First Post",
|
|
slug: "",
|
|
body: "body",
|
|
authorName: "Tester",
|
|
featuredImageUrl: null,
|
|
featuredImageAlt: null,
|
|
status: "draft",
|
|
tagIds: [],
|
|
newTagNames: [],
|
|
...overrides,
|
|
});
|
|
|
|
beforeEach(resetDb);
|
|
|
|
describe("post slug generation", () => {
|
|
it("derives the slug from the title when left blank", async () => {
|
|
const post = await createPost(input());
|
|
expect(post.slug).toBe("my-first-post");
|
|
});
|
|
|
|
it("auto-suffixes generated slugs on duplicate titles", async () => {
|
|
const a = await createPost(input());
|
|
const b = await createPost(input());
|
|
const c = await createPost(input());
|
|
expect(a.slug).toBe("my-first-post");
|
|
expect(b.slug).toBe("my-first-post-2");
|
|
expect(c.slug).toBe("my-first-post-3");
|
|
});
|
|
|
|
it("normalizes an explicitly chosen slug", async () => {
|
|
const post = await createPost(input({ slug: " My Custom SLUG! " }));
|
|
expect(post.slug).toBe("my-custom-slug");
|
|
});
|
|
|
|
it("rejects an explicit slug that another post owns", async () => {
|
|
await createPost(input({ slug: "taken" }));
|
|
await expect(createPost(input({ slug: "taken" }))).rejects.toBeInstanceOf(
|
|
SlugConflictError,
|
|
);
|
|
});
|
|
|
|
it("lets a post keep its own slug on update", async () => {
|
|
const post = await createPost(input({ slug: "keeper" }));
|
|
const updated = await updatePost(post.id, input({ slug: "keeper", title: "New title" }));
|
|
expect(updated?.slug).toBe("keeper");
|
|
expect(updated?.title).toBe("New title");
|
|
});
|
|
|
|
it("rejects updating to a slug owned by another post", async () => {
|
|
await createPost(input({ slug: "occupied" }));
|
|
const other = await createPost(input({ slug: "elsewhere" }));
|
|
await expect(updatePost(other.id, input({ slug: "occupied" }))).rejects.toBeInstanceOf(
|
|
SlugConflictError,
|
|
);
|
|
});
|
|
|
|
it("regenerates from the title when the slug is cleared on update", async () => {
|
|
const post = await createPost(input({ slug: "old-slug" }));
|
|
const updated = await updatePost(post.id, input({ slug: "", title: "Renamed Post" }));
|
|
expect(updated?.slug).toBe("renamed-post");
|
|
});
|
|
});
|
|
|
|
describe("page slugs", () => {
|
|
it("share the same policy but live in their own namespace", async () => {
|
|
const page = await createPage({ title: "About", slug: "", body: "", status: "draft" });
|
|
expect(page.slug).toBe("about");
|
|
// A post may use "about" too — pages and posts have separate URL spaces.
|
|
const post = await createPost(input({ slug: "about" }));
|
|
expect(post.slug).toBe("about");
|
|
await expect(
|
|
createPage({ title: "About", slug: "about", body: "", status: "draft" }),
|
|
).rejects.toBeInstanceOf(SlugConflictError);
|
|
});
|
|
});
|