test: 添加完整测试套件(52 个文件,326 个用例)

基于 Vitest 搭建测试基础设施,覆盖后端纯函数、API 路由、
前端 hooks、UI 组件和页面级集成测试。
This commit is contained in:
2026-02-28 20:19:14 +08:00
parent 11eeec868e
commit 3ccd1262f9
59 changed files with 8131 additions and 3 deletions
+29
View File
@@ -0,0 +1,29 @@
import { NextRequest } from "next/server";
export function createRequest(
url: string,
options: {
method?: string;
body?: unknown;
headers?: Record<string, string>;
} = {},
): NextRequest {
const { method = "GET", body, headers = {} } = options;
const init: RequestInit = { method, headers };
if (body !== undefined) {
init.body = JSON.stringify(body);
(init.headers as Record<string, string>)["content-type"] = "application/json";
}
return new NextRequest(new URL(url, "http://localhost:3000"), init);
}
export function createRouteContext(
params: Record<string, string>,
): { params: Promise<Record<string, string>> } {
return { params: Promise.resolve(params) };
}
export async function parseJsonResponse(response: Response) {
const data = await response.json();
return { status: response.status, data };
}
+123
View File
@@ -0,0 +1,123 @@
import type { Restaurant } from "@/types";
export const TEST_USER = {
id: "user-1",
username: "testuser",
avatar: "🐱",
email: null,
passwordHash: "$2a$10$hashedpassword",
preferences: "{}",
createdAt: new Date("2025-01-01"),
};
export const TEST_USER_2 = {
id: "user-2",
username: "testuser2",
avatar: "🐶",
email: null,
passwordHash: "$2a$10$hashedpassword2",
preferences: "{}",
createdAt: new Date("2025-01-02"),
};
export const TEST_RESTAURANT: Restaurant = {
id: "rest-1",
name: "测试餐厅",
rating: 4.5,
price: "¥80",
distance: "500m",
images: ["https://example.com/img.jpg"],
category: "中餐",
address: "测试地址",
openTime: "09:00-22:00",
tel: "021-12345678",
tag: "川菜",
location: "121.4,31.2",
};
export const TEST_RESTAURANT_2: Restaurant = {
id: "rest-2",
name: "测试餐厅2",
rating: 4.0,
price: "¥60",
distance: "800m",
images: ["https://example.com/img2.jpg"],
category: "日料",
address: "测试地址2",
openTime: "10:00-21:00",
tel: "021-87654321",
tag: "寿司",
location: "121.5,31.3",
};
export const TEST_RESTAURANT_3: Restaurant = {
id: "rest-3",
name: "测试餐厅3",
rating: 3.5,
price: "¥120",
distance: "1200m",
images: ["https://example.com/img3.jpg"],
category: "西餐",
address: "测试地址3",
openTime: "11:00-23:00",
tel: "021-11111111",
tag: "牛排",
location: "121.6,31.4",
};
export const TEST_ROOM_DATA = {
users: [TEST_USER.id, TEST_USER_2.id],
restaurants: [TEST_RESTAURANT, TEST_RESTAURANT_2, TEST_RESTAURANT_3],
likes: {} as Record<string, string[]>,
swipeCounts: {} as Record<string, number>,
match: null as string | null,
creatorId: TEST_USER.id,
locked: false,
kickedUsers: [] as string[],
scene: "eat" as const,
};
export const TEST_BLINDBOX_ROOM = {
id: "bb-room-1",
code: "ABC123",
name: "我们的周末",
creatorId: TEST_USER.id,
city: null,
lat: null,
lng: null,
createdAt: new Date("2025-01-01"),
};
export const TEST_BLINDBOX_IDEA = {
id: "idea-1",
roomId: "bb-room-1",
userId: TEST_USER.id,
content: "去公园野餐",
status: "in_pool",
category: "outdoor",
timeSlot: "morning",
estimatedMinutes: 120,
outdoor: true,
searchQuery: "公园",
searchType: "category",
drawnById: null,
createdAt: new Date("2025-01-01"),
};
export const TEST_WEEKEND_PLAN = {
id: "plan-1",
roomId: "bb-room-1",
userId: TEST_USER.id,
status: "active",
planData: JSON.stringify({
days: [{
date: "周六",
items: [
{ time: "10:00", activity: "去公园", poi: "某公园", address: "xx路", lat: 31.2, lng: 121.4, duration: 120, reason: "天气好" },
],
}],
summary: "愉快的一天",
}),
endTime: null,
createdAt: new Date("2025-01-01"),
};
+13
View File
@@ -0,0 +1,13 @@
import { vi } from "vitest";
import { mockDeep, mockReset, type DeepMockProxy } from "vitest-mock-extended";
import type { PrismaClient } from "@prisma/client";
export const prismaMock = mockDeep<PrismaClient>();
vi.mock("@/lib/prisma", () => ({
prisma: prismaMock,
}));
export function resetPrismaMock() {
mockReset(prismaMock);
}
+6
View File
@@ -0,0 +1,6 @@
import "@testing-library/jest-dom/vitest";
import { afterEach, vi } from "vitest";
afterEach(() => {
vi.restoreAllMocks();
});