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
+127
View File
@@ -0,0 +1,127 @@
import { describe, it, expect } from "vitest";
import { ApiError } from "@/lib/api";
import {
validateUsername,
validatePassword,
validateEmail,
validateIdeaContent,
validateRoomName,
requireString,
} from "@/lib/validation";
describe("validateUsername", () => {
it("accepts 2-16 character names", () => {
expect(validateUsername("ab")).toBe("ab");
expect(validateUsername("a".repeat(16))).toBe("a".repeat(16));
expect(validateUsername("用户名")).toBe("用户名");
});
it("trims whitespace", () => {
expect(validateUsername(" hello ")).toBe("hello");
});
it("rejects names shorter than 2 chars", () => {
expect(() => validateUsername("a")).toThrow(ApiError);
expect(() => validateUsername("")).toThrow(ApiError);
expect(() => validateUsername(" ")).toThrow(ApiError);
});
it("rejects names longer than 16 chars", () => {
expect(() => validateUsername("a".repeat(17))).toThrow(ApiError);
});
});
describe("validatePassword", () => {
it("accepts 6-128 character passwords", () => {
expect(() => validatePassword("123456")).not.toThrow();
expect(() => validatePassword("a".repeat(128))).not.toThrow();
});
it("rejects passwords shorter than 6 chars", () => {
expect(() => validatePassword("12345")).toThrow(ApiError);
expect(() => validatePassword("")).toThrow(ApiError);
});
it("rejects passwords longer than 128 chars", () => {
expect(() => validatePassword("a".repeat(129))).toThrow(ApiError);
});
it("uses custom label in error message", () => {
expect(() => validatePassword("12345", "新密码")).toThrow("新密码至少 6 个字符");
});
});
describe("validateEmail", () => {
it("accepts valid emails", () => {
expect(() => validateEmail("test@example.com")).not.toThrow();
expect(() => validateEmail("user.name@domain.co")).not.toThrow();
});
it("rejects invalid emails", () => {
expect(() => validateEmail("notanemail")).toThrow(ApiError);
expect(() => validateEmail("@domain.com")).toThrow(ApiError);
expect(() => validateEmail("user@")).toThrow(ApiError);
expect(() => validateEmail("user @domain.com")).toThrow(ApiError);
});
});
describe("validateIdeaContent", () => {
it("accepts valid content and trims", () => {
expect(validateIdeaContent(" 去公园 ")).toBe("去公园");
expect(validateIdeaContent("a")).toBe("a");
});
it("rejects empty/falsy content", () => {
expect(() => validateIdeaContent("")).toThrow(ApiError);
expect(() => validateIdeaContent(null)).toThrow(ApiError);
expect(() => validateIdeaContent(undefined)).toThrow(ApiError);
expect(() => validateIdeaContent(" ")).toThrow(ApiError);
});
it("rejects non-string content", () => {
expect(() => validateIdeaContent(123)).toThrow(ApiError);
});
it("rejects content over 200 chars", () => {
expect(() => validateIdeaContent("a".repeat(201))).toThrow(ApiError);
expect(validateIdeaContent("a".repeat(200))).toBe("a".repeat(200));
});
});
describe("validateRoomName", () => {
it("accepts valid room name", () => {
expect(validateRoomName("我的房间")).toBe("我的房间");
});
it("uses fallback for empty name", () => {
expect(validateRoomName("")).toBe("我们的周末");
expect(validateRoomName(null)).toBe("我们的周末");
expect(validateRoomName(undefined)).toBe("我们的周末");
});
it("uses custom fallback", () => {
expect(validateRoomName("", "默认名")).toBe("默认名");
});
it("rejects names over 30 chars", () => {
expect(() => validateRoomName("a".repeat(31))).toThrow(ApiError);
expect(validateRoomName("a".repeat(30))).toBe("a".repeat(30));
});
});
describe("requireString", () => {
it("returns string value when valid", () => {
expect(requireString("hello", "field")).toBe("hello");
});
it("throws for empty/falsy values", () => {
expect(() => requireString("", "字段")).toThrow("字段不能为空");
expect(() => requireString(null, "字段")).toThrow(ApiError);
expect(() => requireString(undefined, "字段")).toThrow(ApiError);
expect(() => requireString(" ", "字段")).toThrow(ApiError);
});
it("throws for non-string values", () => {
expect(() => requireString(123, "字段")).toThrow(ApiError);
});
});