test: 添加完整测试套件(52 个文件,326 个用例)
基于 Vitest 搭建测试基础设施,覆盖后端纯函数、API 路由、 前端 hooks、UI 组件和页面级集成测试。
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
@@ -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"),
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { afterEach, vi } from "vitest";
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
Reference in New Issue
Block a user