Owner.expoPushToken + owner.registerPushToken/clearPushToken, plus a best-effort Expo push send wired into notify() alongside the existing in-app Notification row. Missing/expired token or unreachable Expo push service silently no-ops — push delivery must never break the underlying action it's attached to. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
165 lines
5.7 KiB
TypeScript
165 lines
5.7 KiB
TypeScript
/**
|
|
* Owner Router Unit Tests
|
|
*
|
|
* Covers the account-bound Dark Mode preference (Gitea #8): the persisted
|
|
* value is the source of truth for cross-device sync, while next-themes'
|
|
* own localStorage handles same-browser flash-free reloads.
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from "vitest";
|
|
|
|
vi.mock("server-only", () => ({}));
|
|
vi.mock("@clerk/nextjs/server", () => ({
|
|
auth: vi.fn().mockResolvedValue({ userId: "owner-1" }),
|
|
}));
|
|
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
|
|
|
import { createMockPrisma } from "@/__tests__/helpers/prisma-mock";
|
|
|
|
async function createOwnerCaller(overrides?: {
|
|
userId?: string | null;
|
|
prisma?: ReturnType<typeof createMockPrisma>;
|
|
}) {
|
|
const { ownerRouter } = await import("@/trpc/routers/owner");
|
|
const { initTRPC } = await import("@trpc/server");
|
|
|
|
const mockPrisma = overrides?.prisma ?? createMockPrisma();
|
|
const userId = overrides?.userId !== undefined ? overrides.userId : "owner-1";
|
|
|
|
const t = initTRPC
|
|
.context<{ userId: string | null; prisma: typeof mockPrisma }>()
|
|
.create();
|
|
|
|
const testRouter = t.router({ owner: ownerRouter });
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const caller = testRouter.createCaller({ userId, prisma: mockPrisma as any });
|
|
return { caller, mockPrisma };
|
|
}
|
|
|
|
describe("owner.getThemePreference", () => {
|
|
it("returns null when the Owner row does not exist yet", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.findUnique.mockResolvedValue(null);
|
|
|
|
const result = await caller.owner.getThemePreference();
|
|
|
|
expect(result).toEqual({ themePreference: null });
|
|
});
|
|
|
|
it("returns null when the Owner exists but never set a preference", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.findUnique.mockResolvedValue({ themePreference: null });
|
|
|
|
const result = await caller.owner.getThemePreference();
|
|
|
|
expect(result).toEqual({ themePreference: null });
|
|
});
|
|
|
|
it("returns the stored preference when set", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.findUnique.mockResolvedValue({ themePreference: "DARK" });
|
|
|
|
const result = await caller.owner.getThemePreference();
|
|
|
|
expect(result).toEqual({ themePreference: "DARK" });
|
|
});
|
|
});
|
|
|
|
describe("owner.setThemePreference", () => {
|
|
it("upserts the preference on the calling owner (works even before the Owner row exists)", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.upsert.mockResolvedValue({ id: "owner-1" });
|
|
|
|
const result = await caller.owner.setThemePreference({ themePreference: "DARK" });
|
|
|
|
expect(mockPrisma.owner.upsert).toHaveBeenCalledWith({
|
|
where: { id: "owner-1" },
|
|
create: { id: "owner-1", themePreference: "DARK" },
|
|
update: { themePreference: "DARK" },
|
|
});
|
|
expect(result).toEqual({ ok: true });
|
|
});
|
|
|
|
it("rejects an invalid preference value", async () => {
|
|
const { caller } = await createOwnerCaller();
|
|
|
|
await expect(
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
caller.owner.setThemePreference({ themePreference: "BLUE" as any })
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("owner.getHideSpidersPreference", () => {
|
|
it("defaults to false when the Owner row does not exist yet", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.findUnique.mockResolvedValue(null);
|
|
|
|
const result = await caller.owner.getHideSpidersPreference();
|
|
|
|
expect(result).toEqual({ hideSpiders: false });
|
|
});
|
|
|
|
it("returns the stored preference when set", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.findUnique.mockResolvedValue({ hideSpiders: true });
|
|
|
|
const result = await caller.owner.getHideSpidersPreference();
|
|
|
|
expect(result).toEqual({ hideSpiders: true });
|
|
});
|
|
});
|
|
|
|
describe("owner.setHideSpidersPreference", () => {
|
|
it("upserts the preference on the calling owner (works even before the Owner row exists)", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.upsert.mockResolvedValue({ id: "owner-1" });
|
|
|
|
const result = await caller.owner.setHideSpidersPreference({ hideSpiders: true });
|
|
|
|
expect(mockPrisma.owner.upsert).toHaveBeenCalledWith({
|
|
where: { id: "owner-1" },
|
|
create: { id: "owner-1", hideSpiders: true },
|
|
update: { hideSpiders: true },
|
|
});
|
|
expect(result).toEqual({ ok: true });
|
|
});
|
|
});
|
|
|
|
describe("owner.registerPushToken", () => {
|
|
it("upserts the Expo push token on the calling owner", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.upsert.mockResolvedValue({ id: "owner-1" });
|
|
|
|
const result = await caller.owner.registerPushToken({ expoPushToken: "ExponentPushToken[abc]" });
|
|
|
|
expect(mockPrisma.owner.upsert).toHaveBeenCalledWith({
|
|
where: { id: "owner-1" },
|
|
create: { id: "owner-1", expoPushToken: "ExponentPushToken[abc]" },
|
|
update: { expoPushToken: "ExponentPushToken[abc]" },
|
|
});
|
|
expect(result).toEqual({ ok: true });
|
|
});
|
|
|
|
it("rejects an empty token", async () => {
|
|
const { caller } = await createOwnerCaller();
|
|
|
|
await expect(caller.owner.registerPushToken({ expoPushToken: "" })).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("owner.clearPushToken", () => {
|
|
it("clears the stored token for the calling owner", async () => {
|
|
const { caller, mockPrisma } = await createOwnerCaller();
|
|
mockPrisma.owner.updateMany.mockResolvedValue({ count: 1 });
|
|
|
|
const result = await caller.owner.clearPushToken();
|
|
|
|
expect(mockPrisma.owner.updateMany).toHaveBeenCalledWith({
|
|
where: { id: "owner-1" },
|
|
data: { expoPushToken: null },
|
|
});
|
|
expect(result).toEqual({ ok: true });
|
|
});
|
|
});
|