mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-02-12 15:52:47 +00:00
feat(testing): implement vitest test suite
Add comprehensive testing infrastructure with vitest, React Testing Library, and jsdom. Includes tests for: - Utility functions (cn, isChromeBrowser, date formatting, JSON utils) - Variable detection module (7 pattern types, conversion, false positives) - API routes (health check, user registration with validation/auth) 127 tests passing covering critical functionality.
This commit is contained in:
2886
package-lock.json
generated
2886
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -17,7 +17,11 @@
|
||||
"db:setup": "prisma generate && prisma migrate dev && prisma db seed",
|
||||
"setup": "node scripts/setup.js",
|
||||
"generate:examples": "npx tsx scripts/generate-examples.ts",
|
||||
"postinstall": "prisma generate"
|
||||
"postinstall": "prisma generate",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.11.1",
|
||||
@@ -73,19 +77,27 @@
|
||||
"devDependencies": {
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@vitest/coverage-v8": "^2.1.8",
|
||||
"@vitest/ui": "^2.1.8",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.0.7",
|
||||
"jsdom": "^25.0.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"prisma": "^6.19.0",
|
||||
"tailwindcss": "^4",
|
||||
"tsx": "^4.21.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^5"
|
||||
"typescript": "^5",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "24.x"
|
||||
|
||||
66
src/__tests__/api/health.test.ts
Normal file
66
src/__tests__/api/health.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { GET } from "@/app/api/health/route";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
// Mock the db module
|
||||
vi.mock("@/lib/db", () => ({
|
||||
db: {
|
||||
$queryRaw: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("GET /api/health", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should return healthy status when database is connected", async () => {
|
||||
// Mock successful database query
|
||||
vi.mocked(db.$queryRaw).mockResolvedValueOnce([{ "?column?": 1 }]);
|
||||
|
||||
const response = await GET();
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.status).toBe("healthy");
|
||||
expect(data.database).toBe("connected");
|
||||
expect(data.timestamp).toBeDefined();
|
||||
});
|
||||
|
||||
it("should return unhealthy status when database is disconnected", async () => {
|
||||
// Mock database error
|
||||
vi.mocked(db.$queryRaw).mockRejectedValueOnce(new Error("Connection failed"));
|
||||
|
||||
const response = await GET();
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(503);
|
||||
expect(data.status).toBe("unhealthy");
|
||||
expect(data.database).toBe("disconnected");
|
||||
expect(data.error).toBe("Connection failed");
|
||||
expect(data.timestamp).toBeDefined();
|
||||
});
|
||||
|
||||
it("should handle unknown error type", async () => {
|
||||
// Mock non-Error rejection
|
||||
vi.mocked(db.$queryRaw).mockRejectedValueOnce("Unknown error");
|
||||
|
||||
const response = await GET();
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(503);
|
||||
expect(data.status).toBe("unhealthy");
|
||||
expect(data.error).toBe("Unknown error");
|
||||
});
|
||||
|
||||
it("should include ISO timestamp in response", async () => {
|
||||
vi.mocked(db.$queryRaw).mockResolvedValueOnce([{ "?column?": 1 }]);
|
||||
|
||||
const response = await GET();
|
||||
const data = await response.json();
|
||||
|
||||
// Verify timestamp is valid ISO format
|
||||
const timestamp = new Date(data.timestamp);
|
||||
expect(timestamp.toISOString()).toBe(data.timestamp);
|
||||
});
|
||||
});
|
||||
306
src/__tests__/api/register.test.ts
Normal file
306
src/__tests__/api/register.test.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { POST } from "@/app/api/auth/register/route";
|
||||
import { db } from "@/lib/db";
|
||||
import { getConfig } from "@/lib/config";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@/lib/db", () => ({
|
||||
db: {
|
||||
user: {
|
||||
findUnique: vi.fn(),
|
||||
create: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/config", () => ({
|
||||
getConfig: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("bcryptjs", () => ({
|
||||
default: {
|
||||
hash: vi.fn().mockResolvedValue("hashed_password"),
|
||||
},
|
||||
}));
|
||||
|
||||
function createRequest(body: object): Request {
|
||||
return new Request("http://localhost:3000/api/auth/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
describe("POST /api/auth/register", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
// Default: registration is enabled
|
||||
vi.mocked(getConfig).mockResolvedValue({
|
||||
auth: { allowRegistration: true, providers: [] },
|
||||
features: {},
|
||||
});
|
||||
// Default: no existing users
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null);
|
||||
});
|
||||
|
||||
describe("validation", () => {
|
||||
it("should return 400 for missing name", async () => {
|
||||
const request = createRequest({
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("should return 400 for name too short", async () => {
|
||||
const request = createRequest({
|
||||
name: "A",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("should return 400 for missing username", async () => {
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("should return 400 for invalid username format", async () => {
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "invalid-username!",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("should return 400 for invalid email", async () => {
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "invalid-email",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
|
||||
it("should return 400 for password too short", async () => {
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "12345",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("validation_error");
|
||||
});
|
||||
});
|
||||
|
||||
describe("registration disabled", () => {
|
||||
it("should return 403 when registration is disabled", async () => {
|
||||
vi.mocked(getConfig).mockResolvedValue({
|
||||
auth: { allowRegistration: false, providers: [] },
|
||||
features: {},
|
||||
});
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(data.error).toBe("registration_disabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe("duplicate checks", () => {
|
||||
it("should return 400 when email already exists", async () => {
|
||||
// Mock: email check finds existing user
|
||||
vi.mocked(db.user.findUnique).mockImplementation(async (args) => {
|
||||
if (args?.where?.email) {
|
||||
return { id: "1", email: "test@example.com" } as never;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("email_taken");
|
||||
});
|
||||
|
||||
it("should return 400 when username already exists", async () => {
|
||||
// Mock: email check passes, username check finds existing user
|
||||
vi.mocked(db.user.findUnique).mockImplementation(async (args) => {
|
||||
if (args?.where?.email) {
|
||||
return null;
|
||||
}
|
||||
if (args?.where?.username) {
|
||||
return { id: "1", username: "testuser" } as never;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data.error).toBe("username_taken");
|
||||
});
|
||||
});
|
||||
|
||||
describe("successful registration", () => {
|
||||
it("should create user and return user data", async () => {
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null);
|
||||
vi.mocked(db.user.create).mockResolvedValue({
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "hashed_password",
|
||||
emailVerified: null,
|
||||
image: null,
|
||||
role: "USER",
|
||||
bio: null,
|
||||
credits: 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.id).toBe("user-123");
|
||||
expect(data.name).toBe("Test User");
|
||||
expect(data.username).toBe("testuser");
|
||||
expect(data.email).toBe("test@example.com");
|
||||
expect(data.password).toBeUndefined(); // Password should not be returned
|
||||
});
|
||||
|
||||
it("should accept valid username with underscores", async () => {
|
||||
vi.mocked(db.user.findUnique).mockResolvedValue(null);
|
||||
vi.mocked(db.user.create).mockResolvedValue({
|
||||
id: "user-123",
|
||||
name: "Test User",
|
||||
username: "test_user_123",
|
||||
email: "test@example.com",
|
||||
password: "hashed_password",
|
||||
emailVerified: null,
|
||||
image: null,
|
||||
role: "USER",
|
||||
bio: null,
|
||||
credits: 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "test_user_123",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should return 500 on database error", async () => {
|
||||
vi.mocked(db.user.findUnique).mockRejectedValue(new Error("DB Error"));
|
||||
|
||||
const request = createRequest({
|
||||
name: "Test User",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(data.error).toBe("server_error");
|
||||
});
|
||||
|
||||
it("should return 500 on invalid JSON body", async () => {
|
||||
const request = new Request("http://localhost:3000/api/auth/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: "invalid json",
|
||||
});
|
||||
|
||||
const response = await POST(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(data.error).toBe("server_error");
|
||||
});
|
||||
});
|
||||
});
|
||||
146
src/__tests__/lib/date.test.ts
Normal file
146
src/__tests__/lib/date.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { getDateLocale, formatDistanceToNow, formatDate } from "@/lib/date";
|
||||
import { enUS, tr, es, zhCN, ja, arSA } from "date-fns/locale";
|
||||
|
||||
describe("getDateLocale", () => {
|
||||
it("should return enUS for 'en' locale", () => {
|
||||
expect(getDateLocale("en")).toBe(enUS);
|
||||
});
|
||||
|
||||
it("should return tr for 'tr' locale", () => {
|
||||
expect(getDateLocale("tr")).toBe(tr);
|
||||
});
|
||||
|
||||
it("should return es for 'es' locale", () => {
|
||||
expect(getDateLocale("es")).toBe(es);
|
||||
});
|
||||
|
||||
it("should return zhCN for 'zh' locale", () => {
|
||||
expect(getDateLocale("zh")).toBe(zhCN);
|
||||
});
|
||||
|
||||
it("should return ja for 'ja' locale", () => {
|
||||
expect(getDateLocale("ja")).toBe(ja);
|
||||
});
|
||||
|
||||
it("should return arSA for 'ar' locale", () => {
|
||||
expect(getDateLocale("ar")).toBe(arSA);
|
||||
});
|
||||
|
||||
it("should return enUS for unknown locale", () => {
|
||||
expect(getDateLocale("unknown")).toBe(enUS);
|
||||
expect(getDateLocale("fr")).toBe(enUS);
|
||||
expect(getDateLocale("de")).toBe(enUS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatDistanceToNow", () => {
|
||||
beforeEach(() => {
|
||||
// Mock the current date to ensure consistent test results
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2024-01-15T12:00:00Z"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("should format a date object relative to now", () => {
|
||||
const pastDate = new Date("2024-01-14T12:00:00Z");
|
||||
const result = formatDistanceToNow(pastDate);
|
||||
expect(result).toContain("day");
|
||||
expect(result).toContain("ago");
|
||||
});
|
||||
|
||||
it("should format a date string relative to now", () => {
|
||||
const pastDateString = "2024-01-14T12:00:00Z";
|
||||
const result = formatDistanceToNow(pastDateString);
|
||||
expect(result).toContain("day");
|
||||
expect(result).toContain("ago");
|
||||
});
|
||||
|
||||
it("should format with different locales", () => {
|
||||
const pastDate = new Date("2024-01-14T12:00:00Z");
|
||||
|
||||
// English
|
||||
const enResult = formatDistanceToNow(pastDate, "en");
|
||||
expect(enResult).toContain("ago");
|
||||
|
||||
// Spanish
|
||||
const esResult = formatDistanceToNow(pastDate, "es");
|
||||
expect(esResult).toContain("hace");
|
||||
|
||||
// Turkish
|
||||
const trResult = formatDistanceToNow(pastDate, "tr");
|
||||
expect(trResult).toContain("önce");
|
||||
});
|
||||
|
||||
it("should handle dates from a week ago", () => {
|
||||
const weekAgo = new Date("2024-01-08T12:00:00Z");
|
||||
const result = formatDistanceToNow(weekAgo);
|
||||
// date-fns may return "7 days ago" instead of "1 week ago"
|
||||
expect(result).toMatch(/days?|week/);
|
||||
expect(result).toContain("ago");
|
||||
});
|
||||
|
||||
it("should handle dates from hours ago", () => {
|
||||
const hoursAgo = new Date("2024-01-15T10:00:00Z");
|
||||
const result = formatDistanceToNow(hoursAgo);
|
||||
expect(result).toContain("hours");
|
||||
expect(result).toContain("ago");
|
||||
});
|
||||
|
||||
it("should default to en locale when none provided", () => {
|
||||
const pastDate = new Date("2024-01-14T12:00:00Z");
|
||||
const result = formatDistanceToNow(pastDate);
|
||||
expect(result).toContain("ago"); // English suffix
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatDate", () => {
|
||||
it("should format a date object with the given format string", () => {
|
||||
const date = new Date("2024-01-15T12:30:45Z");
|
||||
expect(formatDate(date, "yyyy-MM-dd")).toBe("2024-01-15");
|
||||
});
|
||||
|
||||
it("should format a date string with the given format string", () => {
|
||||
const dateString = "2024-01-15T12:30:45Z";
|
||||
expect(formatDate(dateString, "yyyy-MM-dd")).toBe("2024-01-15");
|
||||
});
|
||||
|
||||
it("should format with various format strings", () => {
|
||||
const date = new Date("2024-06-15T14:30:00Z");
|
||||
|
||||
expect(formatDate(date, "MM/dd/yyyy")).toBe("06/15/2024");
|
||||
expect(formatDate(date, "dd.MM.yyyy")).toBe("15.06.2024");
|
||||
expect(formatDate(date, "MMMM d, yyyy", "en")).toBe("June 15, 2024");
|
||||
});
|
||||
|
||||
it("should format with different locales", () => {
|
||||
const date = new Date("2024-06-15T14:30:00Z");
|
||||
|
||||
// English month name
|
||||
const enResult = formatDate(date, "MMMM", "en");
|
||||
expect(enResult).toBe("June");
|
||||
|
||||
// Spanish month name
|
||||
const esResult = formatDate(date, "MMMM", "es");
|
||||
expect(esResult).toBe("junio");
|
||||
|
||||
// Turkish month name
|
||||
const trResult = formatDate(date, "MMMM", "tr");
|
||||
expect(trResult).toBe("Haziran");
|
||||
});
|
||||
|
||||
it("should handle time formatting", () => {
|
||||
const date = new Date("2024-01-15T14:30:45Z");
|
||||
expect(formatDate(date, "HH:mm:ss")).toBe("14:30:45");
|
||||
expect(formatDate(date, "h:mm a", "en")).toMatch(/2:30 PM/i);
|
||||
});
|
||||
|
||||
it("should default to en locale when none provided", () => {
|
||||
const date = new Date("2024-01-15T12:00:00Z");
|
||||
const result = formatDate(date, "EEEE"); // Day of week
|
||||
expect(result).toBe("Monday");
|
||||
});
|
||||
});
|
||||
162
src/__tests__/lib/format.test.ts
Normal file
162
src/__tests__/lib/format.test.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { prettifyJson, isValidJson } from "@/lib/format";
|
||||
|
||||
describe("prettifyJson", () => {
|
||||
it("should prettify valid JSON with proper indentation", () => {
|
||||
const input = '{"name":"John","age":30}';
|
||||
const expected = `{
|
||||
"name": "John",
|
||||
"age": 30
|
||||
}`;
|
||||
expect(prettifyJson(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should prettify nested JSON objects", () => {
|
||||
const input = '{"user":{"name":"John","address":{"city":"NYC"}}}';
|
||||
const result = prettifyJson(input);
|
||||
expect(result).toContain('"user"');
|
||||
expect(result).toContain('"address"');
|
||||
expect(result).toContain('"city"');
|
||||
expect(result.split("\n").length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it("should prettify JSON arrays", () => {
|
||||
const input = '[1,2,3,4,5]';
|
||||
const expected = `[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]`;
|
||||
expect(prettifyJson(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should prettify mixed arrays and objects", () => {
|
||||
const input = '{"items":[{"id":1},{"id":2}]}';
|
||||
const result = prettifyJson(input);
|
||||
expect(result).toContain('"items"');
|
||||
expect(result).toContain('"id"');
|
||||
expect(result.split("\n").length).toBeGreaterThan(3);
|
||||
});
|
||||
|
||||
it("should return original content for invalid JSON", () => {
|
||||
const invalidJson = "not valid json";
|
||||
expect(prettifyJson(invalidJson)).toBe(invalidJson);
|
||||
});
|
||||
|
||||
it("should return original content for malformed JSON", () => {
|
||||
const malformed = '{"name": "John",}';
|
||||
expect(prettifyJson(malformed)).toBe(malformed);
|
||||
});
|
||||
|
||||
it("should handle empty object", () => {
|
||||
expect(prettifyJson("{}")).toBe("{}");
|
||||
});
|
||||
|
||||
it("should handle empty array", () => {
|
||||
expect(prettifyJson("[]")).toBe("[]");
|
||||
});
|
||||
|
||||
it("should handle JSON with special characters", () => {
|
||||
const input = '{"message":"Hello\\nWorld"}';
|
||||
const result = prettifyJson(input);
|
||||
expect(result).toContain('"message"');
|
||||
expect(result).toContain("Hello\\nWorld");
|
||||
});
|
||||
|
||||
it("should handle JSON with unicode characters", () => {
|
||||
const input = '{"emoji":"\\u2764","text":"Hello"}';
|
||||
const result = prettifyJson(input);
|
||||
expect(result).toContain('"emoji"');
|
||||
});
|
||||
|
||||
it("should handle boolean and null values", () => {
|
||||
const input = '{"active":true,"deleted":false,"data":null}';
|
||||
const result = prettifyJson(input);
|
||||
expect(result).toContain("true");
|
||||
expect(result).toContain("false");
|
||||
expect(result).toContain("null");
|
||||
});
|
||||
|
||||
it("should handle numeric values", () => {
|
||||
const input = '{"int":42,"float":3.14,"negative":-10}';
|
||||
const result = prettifyJson(input);
|
||||
expect(result).toContain("42");
|
||||
expect(result).toContain("3.14");
|
||||
expect(result).toContain("-10");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isValidJson", () => {
|
||||
it("should return true for valid JSON object", () => {
|
||||
expect(isValidJson('{"name":"John"}')).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for valid JSON array", () => {
|
||||
expect(isValidJson("[1,2,3]")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for empty object", () => {
|
||||
expect(isValidJson("{}")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for empty array", () => {
|
||||
expect(isValidJson("[]")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for JSON string primitive", () => {
|
||||
expect(isValidJson('"hello"')).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for JSON number primitive", () => {
|
||||
expect(isValidJson("42")).toBe(true);
|
||||
expect(isValidJson("3.14")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for JSON boolean", () => {
|
||||
expect(isValidJson("true")).toBe(true);
|
||||
expect(isValidJson("false")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for JSON null", () => {
|
||||
expect(isValidJson("null")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for invalid JSON", () => {
|
||||
expect(isValidJson("not json")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for malformed JSON with trailing comma", () => {
|
||||
expect(isValidJson('{"name": "John",}')).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for single quotes (non-standard)", () => {
|
||||
expect(isValidJson("{'name': 'John'}")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for unquoted keys", () => {
|
||||
expect(isValidJson("{name: 'John'}")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for empty string", () => {
|
||||
expect(isValidJson("")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for undefined-like string", () => {
|
||||
expect(isValidJson("undefined")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for nested valid JSON", () => {
|
||||
const nested = '{"level1":{"level2":{"level3":"value"}}}';
|
||||
expect(isValidJson(nested)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for JSON with whitespace", () => {
|
||||
const withWhitespace = `{
|
||||
"name": "John",
|
||||
"age": 30
|
||||
}`;
|
||||
expect(isValidJson(withWhitespace)).toBe(true);
|
||||
});
|
||||
});
|
||||
115
src/__tests__/lib/utils.test.ts
Normal file
115
src/__tests__/lib/utils.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { cn, isChromeBrowser } from "@/lib/utils";
|
||||
|
||||
describe("cn (className utility)", () => {
|
||||
it("should merge class names", () => {
|
||||
expect(cn("foo", "bar")).toBe("foo bar");
|
||||
});
|
||||
|
||||
it("should handle conditional classes", () => {
|
||||
expect(cn("foo", false && "bar", "baz")).toBe("foo baz");
|
||||
expect(cn("foo", true && "bar", "baz")).toBe("foo bar baz");
|
||||
});
|
||||
|
||||
it("should merge tailwind classes correctly", () => {
|
||||
expect(cn("px-2 py-1", "px-4")).toBe("py-1 px-4");
|
||||
expect(cn("text-red-500", "text-blue-500")).toBe("text-blue-500");
|
||||
});
|
||||
|
||||
it("should handle arrays of class names", () => {
|
||||
expect(cn(["foo", "bar"], "baz")).toBe("foo bar baz");
|
||||
});
|
||||
|
||||
it("should handle objects with boolean values", () => {
|
||||
expect(cn({ foo: true, bar: false, baz: true })).toBe("foo baz");
|
||||
});
|
||||
|
||||
it("should handle undefined and null values", () => {
|
||||
expect(cn("foo", undefined, null, "bar")).toBe("foo bar");
|
||||
});
|
||||
|
||||
it("should handle empty strings", () => {
|
||||
expect(cn("foo", "", "bar")).toBe("foo bar");
|
||||
});
|
||||
|
||||
it("should return empty string for no arguments", () => {
|
||||
expect(cn()).toBe("");
|
||||
});
|
||||
|
||||
it("should handle complex tailwind class conflicts", () => {
|
||||
// Background color conflict
|
||||
expect(cn("bg-red-500", "bg-blue-500")).toBe("bg-blue-500");
|
||||
// Margin conflict
|
||||
expect(cn("m-2", "m-4")).toBe("m-4");
|
||||
// Padding with direction conflict
|
||||
expect(cn("p-2", "px-4")).toBe("p-2 px-4");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isChromeBrowser", () => {
|
||||
const originalWindow = global.window;
|
||||
const originalNavigator = global.navigator;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset window and navigator before each test
|
||||
vi.stubGlobal("window", { ...originalWindow });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("should return false when window is undefined (SSR)", () => {
|
||||
vi.stubGlobal("window", undefined);
|
||||
expect(isChromeBrowser()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for Chrome browser", () => {
|
||||
vi.stubGlobal("navigator", {
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
});
|
||||
expect(isChromeBrowser()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for Edge browser", () => {
|
||||
vi.stubGlobal("navigator", {
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
|
||||
});
|
||||
expect(isChromeBrowser()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for Opera browser", () => {
|
||||
vi.stubGlobal("navigator", {
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0",
|
||||
});
|
||||
expect(isChromeBrowser()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for Brave browser", () => {
|
||||
vi.stubGlobal("navigator", {
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
brave: { isBrave: () => Promise.resolve(true) },
|
||||
});
|
||||
expect(isChromeBrowser()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for Firefox browser", () => {
|
||||
vi.stubGlobal("navigator", {
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
||||
});
|
||||
expect(isChromeBrowser()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for Safari browser", () => {
|
||||
vi.stubGlobal("navigator", {
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
|
||||
});
|
||||
expect(isChromeBrowser()).toBe(false);
|
||||
});
|
||||
});
|
||||
352
src/__tests__/lib/variable-detection.test.ts
Normal file
352
src/__tests__/lib/variable-detection.test.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
detectVariables,
|
||||
convertToSupportedFormat,
|
||||
convertAllVariables,
|
||||
getPatternDescription,
|
||||
type DetectedVariable,
|
||||
} from "@/lib/variable-detection";
|
||||
|
||||
describe("detectVariables", () => {
|
||||
describe("double bracket pattern [[...]]", () => {
|
||||
it("should detect [[name]] format", () => {
|
||||
const result = detectVariables("Hello [[name]]!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("name");
|
||||
expect(result[0].pattern).toBe("double_bracket");
|
||||
expect(result[0].original).toBe("[[name]]");
|
||||
});
|
||||
|
||||
it("should detect [[name]] with spaces", () => {
|
||||
const result = detectVariables("Hello [[ name ]]!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("name");
|
||||
});
|
||||
|
||||
it("should detect [[name: default]] with default value", () => {
|
||||
const result = detectVariables("Hello [[name: John]]!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("name");
|
||||
expect(result[0].defaultValue).toBe("John");
|
||||
});
|
||||
|
||||
it("should detect multiple [[...]] variables", () => {
|
||||
const result = detectVariables("Hello [[first_name]] [[last_name]]!");
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].name).toBe("first_name");
|
||||
expect(result[1].name).toBe("last_name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("double curly pattern {{...}}", () => {
|
||||
it("should detect {{name}} format", () => {
|
||||
const result = detectVariables("Hello {{name}}!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("name");
|
||||
expect(result[0].pattern).toBe("double_curly");
|
||||
});
|
||||
|
||||
it("should detect {{name}} with spaces", () => {
|
||||
const result = detectVariables("Hello {{ name }}!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("name");
|
||||
});
|
||||
|
||||
it("should detect {{name: default}} with default value", () => {
|
||||
const result = detectVariables("Hello {{name: Jane}}!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("name");
|
||||
expect(result[0].defaultValue).toBe("Jane");
|
||||
});
|
||||
});
|
||||
|
||||
describe("single bracket pattern [...]", () => {
|
||||
it("should detect [NAME] uppercase format", () => {
|
||||
const result = detectVariables("Hello [NAME]!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("NAME");
|
||||
expect(result[0].pattern).toBe("single_bracket");
|
||||
});
|
||||
|
||||
it("should detect [Your Name] multi-word format", () => {
|
||||
const result = detectVariables("Hello [Your Name]!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("Your Name");
|
||||
});
|
||||
|
||||
it("should detect [USER_ID] with underscores", () => {
|
||||
const result = detectVariables("ID: [USER_ID]");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("USER_ID");
|
||||
});
|
||||
});
|
||||
|
||||
describe("single curly pattern {...}", () => {
|
||||
it("should detect {NAME} uppercase format", () => {
|
||||
const result = detectVariables("Hello {NAME}!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("NAME");
|
||||
expect(result[0].pattern).toBe("single_curly");
|
||||
});
|
||||
|
||||
it("should detect {USER_ID} with underscores", () => {
|
||||
const result = detectVariables("ID: {USER_ID}");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("USER_ID");
|
||||
});
|
||||
});
|
||||
|
||||
describe("angle bracket pattern <...>", () => {
|
||||
it("should detect <NAME> uppercase format", () => {
|
||||
const result = detectVariables("Hello <NAME>!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("NAME");
|
||||
expect(result[0].pattern).toBe("angle_bracket");
|
||||
});
|
||||
|
||||
it("should detect <Your Name> with spaces", () => {
|
||||
const result = detectVariables("Hello <Your Name>!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("Your Name");
|
||||
});
|
||||
|
||||
it("should NOT detect common HTML tags", () => {
|
||||
const result = detectVariables("<div>content</div>");
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should NOT detect lowercase single words (looks like HTML)", () => {
|
||||
const result = detectVariables("<username>");
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("percent pattern %...%", () => {
|
||||
it("should detect %NAME% format", () => {
|
||||
const result = detectVariables("Hello %NAME%!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("NAME");
|
||||
expect(result[0].pattern).toBe("percent");
|
||||
});
|
||||
|
||||
it("should detect %user_name% lowercase with underscore", () => {
|
||||
const result = detectVariables("Hello %user_name%!");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("user_name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("dollar curly pattern ${...} (supported format)", () => {
|
||||
it("should NOT detect ${name} as it is the supported format", () => {
|
||||
const result = detectVariables("Hello ${name}!");
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should NOT detect ${name:default} with default", () => {
|
||||
const result = detectVariables("Hello ${name:John}!");
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("false positive handling", () => {
|
||||
it("should skip HTML tags", () => {
|
||||
const htmlTags = [
|
||||
"<div>",
|
||||
"<span>",
|
||||
"<p>",
|
||||
"<a>",
|
||||
"<button>",
|
||||
"<input>",
|
||||
"<form>",
|
||||
"<table>",
|
||||
];
|
||||
for (const tag of htmlTags) {
|
||||
const result = detectVariables(tag);
|
||||
expect(result).toHaveLength(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("should skip programming keywords", () => {
|
||||
// These would be in angle brackets but should be filtered
|
||||
const result = detectVariables("<IF> <ELSE> <FOR>");
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should skip very short names", () => {
|
||||
const result = detectVariables("[[a]] {{b}}");
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mixed patterns", () => {
|
||||
it("should detect multiple different patterns", () => {
|
||||
const text = "Hello [[name]], your ID is {{user_id}} and role is [ROLE]";
|
||||
const result = detectVariables(text);
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result.map((v) => v.pattern)).toContain("double_bracket");
|
||||
expect(result.map((v) => v.pattern)).toContain("double_curly");
|
||||
expect(result.map((v) => v.pattern)).toContain("single_bracket");
|
||||
});
|
||||
|
||||
it("should handle text with supported format mixed in", () => {
|
||||
const text = "Hello ${name}, welcome [[user]]!";
|
||||
const result = detectVariables(text);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("user");
|
||||
});
|
||||
});
|
||||
|
||||
describe("position tracking", () => {
|
||||
it("should track start and end indices correctly", () => {
|
||||
const text = "Hello [[name]]!";
|
||||
const result = detectVariables(text);
|
||||
expect(result[0].startIndex).toBe(6);
|
||||
expect(result[0].endIndex).toBe(14);
|
||||
expect(text.slice(result[0].startIndex, result[0].endIndex)).toBe(
|
||||
"[[name]]"
|
||||
);
|
||||
});
|
||||
|
||||
it("should sort results by position", () => {
|
||||
const text = "{{second}} first [[first]] text";
|
||||
const result = detectVariables(text);
|
||||
expect(result[0].startIndex).toBeLessThan(result[1].startIndex);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("convertToSupportedFormat", () => {
|
||||
it("should convert variable name to lowercase", () => {
|
||||
const variable: DetectedVariable = {
|
||||
original: "[[NAME]]",
|
||||
name: "NAME",
|
||||
pattern: "double_bracket",
|
||||
startIndex: 0,
|
||||
endIndex: 8,
|
||||
};
|
||||
expect(convertToSupportedFormat(variable)).toBe("${name}");
|
||||
});
|
||||
|
||||
it("should replace spaces with underscores", () => {
|
||||
const variable: DetectedVariable = {
|
||||
original: "[[Your Name]]",
|
||||
name: "Your Name",
|
||||
pattern: "double_bracket",
|
||||
startIndex: 0,
|
||||
endIndex: 13,
|
||||
};
|
||||
expect(convertToSupportedFormat(variable)).toBe("${your_name}");
|
||||
});
|
||||
|
||||
it("should include default value if present", () => {
|
||||
const variable: DetectedVariable = {
|
||||
original: "[[name: John]]",
|
||||
name: "name",
|
||||
defaultValue: "John",
|
||||
pattern: "double_bracket",
|
||||
startIndex: 0,
|
||||
endIndex: 14,
|
||||
};
|
||||
expect(convertToSupportedFormat(variable)).toBe("${name:John}");
|
||||
});
|
||||
|
||||
it("should handle multiple spaces", () => {
|
||||
const variable: DetectedVariable = {
|
||||
original: "[[First Middle Last]]",
|
||||
name: "First Middle Last",
|
||||
pattern: "double_bracket",
|
||||
startIndex: 0,
|
||||
endIndex: 21,
|
||||
};
|
||||
expect(convertToSupportedFormat(variable)).toBe("${first_middle_last}");
|
||||
});
|
||||
|
||||
it("should remove special characters", () => {
|
||||
const variable: DetectedVariable = {
|
||||
original: "[[user@name]]",
|
||||
name: "user@name",
|
||||
pattern: "double_bracket",
|
||||
startIndex: 0,
|
||||
endIndex: 13,
|
||||
};
|
||||
expect(convertToSupportedFormat(variable)).toBe("${username}");
|
||||
});
|
||||
});
|
||||
|
||||
describe("convertAllVariables", () => {
|
||||
it("should convert all detected variables in text", () => {
|
||||
const text = "Hello [[name]], your ID is {{user_id}}";
|
||||
const result = convertAllVariables(text);
|
||||
expect(result).toBe("Hello ${name}, your ID is ${user_id}");
|
||||
});
|
||||
|
||||
it("should preserve text that has no variables", () => {
|
||||
const text = "Hello world, no variables here!";
|
||||
expect(convertAllVariables(text)).toBe(text);
|
||||
});
|
||||
|
||||
it("should handle multiple variables of same pattern", () => {
|
||||
const text = "[[first]] and [[second]] and [[third]]";
|
||||
const result = convertAllVariables(text);
|
||||
expect(result).toBe("${first} and ${second} and ${third}");
|
||||
});
|
||||
|
||||
it("should preserve already supported format", () => {
|
||||
const text = "Hello ${name}!";
|
||||
expect(convertAllVariables(text)).toBe(text);
|
||||
});
|
||||
|
||||
it("should handle mixed supported and unsupported formats", () => {
|
||||
const text = "Hello ${name}, welcome [[user]]!";
|
||||
const result = convertAllVariables(text);
|
||||
expect(result).toBe("Hello ${name}, welcome ${user}!");
|
||||
});
|
||||
|
||||
it("should convert with default values", () => {
|
||||
const text = "Hello [[name: World]]!";
|
||||
const result = convertAllVariables(text);
|
||||
expect(result).toBe("Hello ${name:World}!");
|
||||
});
|
||||
|
||||
it("should handle variables at the start and end", () => {
|
||||
const text = "[[start]] middle [[end]]";
|
||||
const result = convertAllVariables(text);
|
||||
expect(result).toBe("${start} middle ${end}");
|
||||
});
|
||||
|
||||
it("should handle adjacent variables", () => {
|
||||
const text = "[[first]][[second]]";
|
||||
const result = convertAllVariables(text);
|
||||
expect(result).toBe("${first}${second}");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPatternDescription", () => {
|
||||
it("should return correct description for double_bracket", () => {
|
||||
expect(getPatternDescription("double_bracket")).toBe("[[...]]");
|
||||
});
|
||||
|
||||
it("should return correct description for double_curly", () => {
|
||||
expect(getPatternDescription("double_curly")).toBe("{{...}}");
|
||||
});
|
||||
|
||||
it("should return correct description for single_bracket", () => {
|
||||
expect(getPatternDescription("single_bracket")).toBe("[...]");
|
||||
});
|
||||
|
||||
it("should return correct description for single_curly", () => {
|
||||
expect(getPatternDescription("single_curly")).toBe("{...}");
|
||||
});
|
||||
|
||||
it("should return correct description for angle_bracket", () => {
|
||||
expect(getPatternDescription("angle_bracket")).toBe("<...>");
|
||||
});
|
||||
|
||||
it("should return correct description for percent", () => {
|
||||
expect(getPatternDescription("percent")).toBe("%...%");
|
||||
});
|
||||
|
||||
it("should return correct description for dollar_curly", () => {
|
||||
expect(getPatternDescription("dollar_curly")).toBe("${...}");
|
||||
});
|
||||
});
|
||||
31
vitest.config.ts
Normal file
31
vitest.config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: ["./vitest.setup.ts"],
|
||||
include: ["src/**/*.{test,spec}.{ts,tsx}"],
|
||||
exclude: ["node_modules", ".next", "packages"],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["text", "json", "html"],
|
||||
exclude: [
|
||||
"node_modules/",
|
||||
".next/",
|
||||
"packages/",
|
||||
"src/**/*.d.ts",
|
||||
"vitest.config.ts",
|
||||
"vitest.setup.ts",
|
||||
],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
89
vitest.setup.ts
Normal file
89
vitest.setup.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { vi } from "vitest";
|
||||
|
||||
// Mock environment variables
|
||||
process.env.NEXTAUTH_SECRET = "test-secret";
|
||||
process.env.NEXTAUTH_URL = "http://localhost:3000";
|
||||
process.env.DATABASE_URL = "postgresql://test:test@localhost:5432/test";
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
back: vi.fn(),
|
||||
forward: vi.fn(),
|
||||
prefetch: vi.fn(),
|
||||
}),
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
usePathname: () => "/",
|
||||
useParams: () => ({}),
|
||||
redirect: vi.fn(),
|
||||
notFound: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock next/headers
|
||||
vi.mock("next/headers", () => ({
|
||||
cookies: () => ({
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
}),
|
||||
headers: () => new Headers(),
|
||||
}));
|
||||
|
||||
// Mock next-intl
|
||||
vi.mock("next-intl", () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => "en",
|
||||
getTranslations: () => Promise.resolve((key: string) => key),
|
||||
}));
|
||||
|
||||
// Mock Prisma client
|
||||
vi.mock("@/lib/db", () => ({
|
||||
db: {
|
||||
user: {
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
findMany: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
count: vi.fn(),
|
||||
},
|
||||
prompt: {
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
findMany: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
count: vi.fn(),
|
||||
},
|
||||
category: {
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
findMany: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
$queryRaw: vi.fn(),
|
||||
$executeRaw: vi.fn(),
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock config
|
||||
vi.mock("@/lib/config", () => ({
|
||||
getConfig: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
auth: {
|
||||
allowRegistration: true,
|
||||
providers: [],
|
||||
},
|
||||
features: {},
|
||||
})
|
||||
),
|
||||
}));
|
||||
Reference in New Issue
Block a user