148 lines
5 KiB
TypeScript
148 lines
5 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { eq } from "drizzle-orm";
|
|
import { db } from "@/db";
|
|
import { fontEnum, tags, themeEnum } from "@/db/schema";
|
|
import { resolveHomeContent } from "@/lib/services/home";
|
|
import { createPage, deletePage, setPageStatus } from "@/lib/services/pages";
|
|
import { createPost } from "@/lib/services/posts";
|
|
import {
|
|
getSettings,
|
|
listPublicNav,
|
|
saveSettings,
|
|
type SettingsInput,
|
|
} from "@/lib/services/settings";
|
|
import { resetDb } from "../helpers/db";
|
|
|
|
const settingsInput = (overrides: Partial<SettingsInput> = {}): SettingsInput => ({
|
|
siteTitle: "Test Site",
|
|
headerText: "",
|
|
footerText: "",
|
|
postsPerPage: 10,
|
|
excerptWords: 40,
|
|
homeMode: "posts",
|
|
homeTagId: null,
|
|
homePageId: null,
|
|
theme: "solarized-dark",
|
|
font: "geist",
|
|
...overrides,
|
|
});
|
|
|
|
beforeEach(resetDb);
|
|
|
|
describe("home page configuration", () => {
|
|
it("defaults to the post list", async () => {
|
|
await saveSettings(settingsInput(), []);
|
|
const home = await resolveHomeContent(await getSettings());
|
|
expect(home.kind).toBe("posts");
|
|
});
|
|
|
|
it("shows a configured tag and falls back when the tag is deleted", async () => {
|
|
await createPost({
|
|
title: "Tagged",
|
|
slug: "",
|
|
body: "b",
|
|
authorName: "T",
|
|
featuredImageUrl: null,
|
|
featuredImageAlt: null,
|
|
status: "published",
|
|
tagIds: [],
|
|
newTagNames: ["Featured"],
|
|
});
|
|
const [tag] = await db.select().from(tags).where(eq(tags.slug, "featured"));
|
|
await saveSettings(settingsInput({ homeMode: "tag", homeTagId: tag.id }), []);
|
|
|
|
const home = await resolveHomeContent(await getSettings());
|
|
expect(home).toMatchObject({ kind: "tag", tag: { slug: "featured" } });
|
|
|
|
// Deleting the tag nulls settings.home_tag_id via ON DELETE SET NULL.
|
|
await db.delete(tags).where(eq(tags.id, tag.id));
|
|
const settingsAfter = await getSettings();
|
|
expect(settingsAfter.homeTagId).toBeNull();
|
|
expect((await resolveHomeContent(settingsAfter)).kind).toBe("posts");
|
|
});
|
|
|
|
it("shows a configured page and falls back when it is unpublished or deleted", async () => {
|
|
const page = await createPage({
|
|
title: "Welcome",
|
|
slug: "",
|
|
body: "Hello!",
|
|
status: "published",
|
|
});
|
|
await saveSettings(settingsInput({ homeMode: "page", homePageId: page.id }), []);
|
|
|
|
let home = await resolveHomeContent(await getSettings());
|
|
expect(home).toMatchObject({ kind: "page", page: { slug: "welcome" } });
|
|
|
|
// Unpublished page must not leak through the home route.
|
|
await setPageStatus(page.id, "draft");
|
|
home = await resolveHomeContent(await getSettings());
|
|
expect(home.kind).toBe("posts");
|
|
|
|
await setPageStatus(page.id, "published");
|
|
await deletePage(page.id);
|
|
const settingsAfter = await getSettings();
|
|
expect(settingsAfter.homePageId).toBeNull();
|
|
expect((await resolveHomeContent(settingsAfter)).kind).toBe("posts");
|
|
});
|
|
|
|
it("ignores a stale tag reference when the mode is posts", async () => {
|
|
await saveSettings(settingsInput({ homeMode: "posts", homeTagId: null }), []);
|
|
expect((await resolveHomeContent(await getSettings())).kind).toBe("posts");
|
|
});
|
|
});
|
|
|
|
describe("appearance settings", () => {
|
|
it("defaults to solarized-dark and geist on an unseeded database", async () => {
|
|
const settings = await getSettings();
|
|
expect(settings.theme).toBe("solarized-dark");
|
|
expect(settings.font).toBe("geist");
|
|
});
|
|
|
|
it("round-trips every theme in the enum", async () => {
|
|
for (const theme of themeEnum.enumValues) {
|
|
await saveSettings(settingsInput({ theme }), []);
|
|
expect((await getSettings()).theme).toBe(theme);
|
|
}
|
|
});
|
|
|
|
it("round-trips every font in the enum", async () => {
|
|
for (const font of fontEnum.enumValues) {
|
|
await saveSettings(settingsInput({ font }), []);
|
|
expect((await getSettings()).font).toBe(font);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("navigation", () => {
|
|
it("keeps URL items, resolves page items, and hides unpublished pages", async () => {
|
|
const page = await createPage({
|
|
title: "About",
|
|
slug: "",
|
|
body: "",
|
|
status: "published",
|
|
});
|
|
await saveSettings(settingsInput(), [
|
|
{ label: "All posts", url: "/posts", pageId: null },
|
|
{ label: "About", url: null, pageId: page.id },
|
|
{ label: "Elsewhere", url: "https://example.com", pageId: null },
|
|
]);
|
|
|
|
let nav = await listPublicNav();
|
|
expect(nav).toEqual([
|
|
{ label: "All posts", href: "/posts", external: false },
|
|
{ label: "About", href: "/pages/about", external: false },
|
|
{ label: "Elsewhere", href: "https://example.com", external: true },
|
|
]);
|
|
|
|
await setPageStatus(page.id, "draft");
|
|
nav = await listPublicNav();
|
|
expect(nav.map((l) => l.label)).toEqual(["All posts", "Elsewhere"]);
|
|
|
|
// Deleting the page removes its nav item entirely (ON DELETE CASCADE).
|
|
await setPageStatus(page.id, "published");
|
|
await deletePage(page.id);
|
|
nav = await listPublicNav();
|
|
expect(nav.map((l) => l.label)).toEqual(["All posts", "Elsewhere"]);
|
|
});
|
|
});
|