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

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