6bb0e65d4c
Task 4: 统一 amap.ts 为完整 API 客户端 - 扩展 amap.ts 为统一客户端(amapFetch 8s 超时 + 错误处理) - 导出 searchPlaceText/searchPlaceAround/getInputTips/reverseGeocode/getTransitDirection - 精简 4 个 location route 为单行调用,blindboxPlanGen 删除 ~80 行内联 API 代码 Task 2: 抽取 ShareCardShell 消除三兄弟重复 - 新建 ShareCardShell.tsx 共享外框/背景/品牌头/QR 底部 - RestaurantShareCard 406→268 行,BlindboxShareCard 341→173 行,BlindboxPlanShareCard 277→159 行 Task 3: 拆分 BlindboxPlan.tsx (742→371 行) - 提取 planUtils.ts (guessCategory + formatDuration) - 提取 PoiSearchField / SortablePlanItem / PlanItemEditModal 三个独立组件 Task 1: 拆分 blindbox/[code]/page.tsx 上帝组件 (1300→509 行) - 提取 useBlindboxRoom / useBlindboxIdeas / useBlindboxPlan / useBlindboxDraw 四个 hooks - 提取 BlindboxPoolPhase / BlindboxRevealPhase 两个子组件 - 主页面仅保留 phase 协调 + hook 组装 + 子组件渲染 Task 5: 统一 SWR 数据获取层 - 新建 fetcher.ts (FetchError 携带 status,401 不重试) - 新建 useBlindboxRooms / useAchievements / useFavorites SWR hooks - useRoomPolling 改用共享 fetcher - blindbox 大厅/成就/个人中心页面删除手写 fetch 样板代码 - JWT 过期时自动弹出登录框而非反复重试
68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { createRequest, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
|
|
|
|
vi.mock("@/lib/prisma", () => ({ prisma: {} }));
|
|
|
|
const mockSearchPlaceText = vi.fn();
|
|
vi.mock("@/lib/amap", () => ({
|
|
searchPlaceText: (...args: unknown[]) => mockSearchPlaceText(...args),
|
|
}));
|
|
|
|
import { GET } from "./route";
|
|
|
|
const mockCtx = { params: Promise.resolve({}) };
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("GET /api/location/search", () => {
|
|
it("returns search results", async () => {
|
|
mockSearchPlaceText.mockResolvedValue([
|
|
{
|
|
id: "poi-1",
|
|
name: "星巴克",
|
|
address: "南京路1号",
|
|
lat: 31.2,
|
|
lng: 121.4,
|
|
rating: 4.5,
|
|
cost: 40,
|
|
},
|
|
]);
|
|
|
|
const req = createRequest("/api/location/search?keywords=星巴克");
|
|
const res = await GET(req, mockCtx);
|
|
const { status, data } = await parseJsonResponse(res);
|
|
|
|
expect(status).toBe(200);
|
|
expect(data).toHaveLength(1);
|
|
expect(data[0].name).toBe("星巴克");
|
|
expect(data[0].lat).toBe(31.2);
|
|
expect(data[0].lng).toBe(121.4);
|
|
});
|
|
|
|
it("returns empty when no results", async () => {
|
|
mockSearchPlaceText.mockResolvedValue([]);
|
|
|
|
const req = createRequest("/api/location/search?keywords=不存在的地方");
|
|
const res = await GET(req, mockCtx);
|
|
const { data } = await parseJsonResponse(res);
|
|
expect(data).toEqual([]);
|
|
});
|
|
|
|
it("returns 400 when keywords missing", async () => {
|
|
const req = createRequest("/api/location/search");
|
|
const res = await GET(req, mockCtx);
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
it("returns 503 when API unavailable", async () => {
|
|
const { ApiError } = await import("@/lib/api");
|
|
mockSearchPlaceText.mockRejectedValue(new ApiError("位置服务暂时不可用,请稍后重试", 503));
|
|
|
|
const req = createRequest("/api/location/search?keywords=test");
|
|
const res = await GET(req, mockCtx);
|
|
expect(res.status).toBe(503);
|
|
});
|
|
});
|