import { describe, it, expect, beforeEach, beforeAll } from "vitest" import { prisma } from "@/lib/prisma" /** * Integration tests for collections functionality * These tests focus on database operations and data integrity */ describe("Collections Database Operations", () => { let testUser: any let englishLanguage: any beforeEach(async () => { // Note: Global beforeEach in vitest.integration.setup.ts clears all data // So we need to recreate user and language in each test // Create English language englishLanguage = await prisma.language.create({ data: { code: "en", name: "English", nativeName: "English", isActive: true, }, }) // Create test user testUser = await prisma.user.create({ data: { email: "testcollections@example.com", name: "Test User", password: "dummy", role: "USER", isActive: true, }, }) }) describe("Collection CRUD Operations", () => { it("should create a collection", async () => { const collection = await prisma.collection.create({ data: { name: "Test Collection", description: "Test description", isPublic: false, isGlobal: false, createdBy: testUser.id, }, }) expect(collection.id).toBeDefined() expect(collection.name).toBe("Test Collection") expect(collection.createdBy).toBe(testUser.id) }) it("should update a collection", async () => { const collection = await prisma.collection.create({ data: { name: "Test Collection", isPublic: false, isGlobal: false, createdBy: testUser.id, }, }) const updated = await prisma.collection.update({ where: { id: collection.id }, data: { name: "Updated Name", description: "New description", }, }) expect(updated.name).toBe("Updated Name") expect(updated.description).toBe("New description") }) it("should delete a collection", async () => { const collection = await prisma.collection.create({ data: { name: "Test Collection", isPublic: false, isGlobal: false, createdBy: testUser.id, }, }) await prisma.collection.delete({ where: { id: collection.id }, }) const deleted = await prisma.collection.findUnique({ where: { id: collection.id }, }) expect(deleted).toBeNull() }) it("should get user collections", async () => { await prisma.collection.createMany({ data: [ { name: "Collection 1", isPublic: false, isGlobal: false, createdBy: testUser.id, }, { name: "Collection 2", isPublic: true, isGlobal: false, createdBy: testUser.id, }, ], }) const collections = await prisma.collection.findMany({ where: { createdBy: testUser.id }, }) expect(collections.length).toBe(2) }) it("should get global collections", async () => { await prisma.collection.create({ data: { name: "HSK 1", isPublic: true, isGlobal: true, createdBy: null, }, }) const globalCollections = await prisma.collection.findMany({ where: { isGlobal: true }, }) expect(globalCollections.length).toBe(1) expect(globalCollections[0].name).toBe("HSK 1") }) }) describe("Collection Items and OrderIndex", () => { let collection: any let hanzi1: any let hanzi2: any let hanzi3: any beforeEach(async () => { // Create collection collection = await prisma.collection.create({ data: { name: "Test Collection", isPublic: false, isGlobal: false, createdBy: testUser.id, }, }) // Create hanzi hanzi1 = await prisma.hanzi.create({ data: { simplified: "好", radical: "女", frequency: 100 }, }) hanzi2 = await prisma.hanzi.create({ data: { simplified: "爱", radical: "爫", frequency: 200 }, }) hanzi3 = await prisma.hanzi.create({ data: { simplified: "你", radical: "人", frequency: 50 }, }) }) it("should add hanzi to collection with correct orderIndex", async () => { await prisma.collectionItem.createMany({ data: [ { collectionId: collection.id, hanziId: hanzi1.id, orderIndex: 0 }, { collectionId: collection.id, hanziId: hanzi2.id, orderIndex: 1 }, { collectionId: collection.id, hanziId: hanzi3.id, orderIndex: 2 }, ], }) const items = await prisma.collectionItem.findMany({ where: { collectionId: collection.id }, orderBy: { orderIndex: "asc" }, include: { hanzi: true }, }) expect(items.length).toBe(3) expect(items[0].hanzi.simplified).toBe("好") expect(items[0].orderIndex).toBe(0) expect(items[1].hanzi.simplified).toBe("爱") expect(items[1].orderIndex).toBe(1) expect(items[2].hanzi.simplified).toBe("你") expect(items[2].orderIndex).toBe(2) }) it("should preserve orderIndex after removing middle item", async () => { // Add three items await prisma.collectionItem.createMany({ data: [ { collectionId: collection.id, hanziId: hanzi1.id, orderIndex: 0 }, { collectionId: collection.id, hanziId: hanzi2.id, orderIndex: 1 }, { collectionId: collection.id, hanziId: hanzi3.id, orderIndex: 2 }, ], }) // Remove middle item await prisma.collectionItem.deleteMany({ where: { collectionId: collection.id, hanziId: hanzi2.id, }, }) const items = await prisma.collectionItem.findMany({ where: { collectionId: collection.id }, orderBy: { orderIndex: "asc" }, include: { hanzi: true }, }) expect(items.length).toBe(2) expect(items[0].hanzi.simplified).toBe("好") expect(items[0].orderIndex).toBe(0) expect(items[1].hanzi.simplified).toBe("你") expect(items[1].orderIndex).toBe(2) // Should keep original orderIndex }) it("should prevent duplicate hanzi in collection (unique constraint)", async () => { await prisma.collectionItem.create({ data: { collectionId: collection.id, hanziId: hanzi1.id, orderIndex: 0, }, }) // Try to add same hanzi again await expect( prisma.collectionItem.create({ data: { collectionId: collection.id, hanziId: hanzi1.id, orderIndex: 1, }, }) ).rejects.toThrow() }) it("should allow same hanzi in different collections", async () => { const collection2 = await prisma.collection.create({ data: { name: "Second Collection", isPublic: false, isGlobal: false, createdBy: testUser.id, }, }) await prisma.collectionItem.createMany({ data: [ { collectionId: collection.id, hanziId: hanzi1.id, orderIndex: 0 }, { collectionId: collection2.id, hanziId: hanzi1.id, orderIndex: 0 }, ], }) const items1 = await prisma.collectionItem.count({ where: { collectionId: collection.id }, }) const items2 = await prisma.collectionItem.count({ where: { collectionId: collection2.id }, }) expect(items1).toBe(1) expect(items2).toBe(1) }) it("should delete collection items when collection is deleted (cascade)", async () => { await prisma.collectionItem.createMany({ data: [ { collectionId: collection.id, hanziId: hanzi1.id, orderIndex: 0 }, { collectionId: collection.id, hanziId: hanzi2.id, orderIndex: 1 }, ], }) await prisma.collection.delete({ where: { id: collection.id }, }) const items = await prisma.collectionItem.findMany({ where: { collectionId: collection.id }, }) expect(items.length).toBe(0) }) }) describe("Hanzi Search Operations", () => { beforeEach(async () => { // Create hanzi with forms, transcriptions, and meanings const hanzi1 = await prisma.hanzi.create({ data: { simplified: "好", radical: "女", frequency: 100 }, }) const form1 = await prisma.hanziForm.create({ data: { hanziId: hanzi1.id, traditional: "好", isDefault: true, }, }) await prisma.hanziTranscription.create({ data: { formId: form1.id, type: "pinyin", value: "hǎo", }, }) await prisma.hanziMeaning.create({ data: { formId: form1.id, languageId: englishLanguage.id, meaning: "good", orderIndex: 0, }, }) const hanzi2 = await prisma.hanzi.create({ data: { simplified: "你", radical: "人", frequency: 50 }, }) const form2 = await prisma.hanziForm.create({ data: { hanziId: hanzi2.id, traditional: "你", isDefault: true, }, }) await prisma.hanziTranscription.create({ data: { formId: form2.id, type: "pinyin", value: "nǐ", }, }) await prisma.hanziMeaning.create({ data: { formId: form2.id, languageId: englishLanguage.id, meaning: "you", orderIndex: 0, }, }) const hanzi3 = await prisma.hanzi.create({ data: { simplified: "中国", radical: null, frequency: 300 }, }) const form3 = await prisma.hanziForm.create({ data: { hanziId: hanzi3.id, traditional: "中國", isDefault: true, }, }) await prisma.hanziTranscription.create({ data: { formId: form3.id, type: "pinyin", value: "zhōng guó", }, }) await prisma.hanziMeaning.create({ data: { formId: form3.id, languageId: englishLanguage.id, meaning: "China", orderIndex: 0, }, }) }) it("should search hanzi by simplified character", async () => { const results = await prisma.hanzi.findMany({ where: { simplified: { contains: "好" }, }, include: { forms: { where: { isDefault: true }, include: { transcriptions: { where: { type: "pinyin" }, take: 1 }, meanings: { orderBy: { orderIndex: "asc" }, take: 1 }, }, take: 1, }, }, }) expect(results.length).toBeGreaterThan(0) expect(results.some((h) => h.simplified === "好")).toBe(true) }) it("should search hanzi by pinyin transcription", async () => { const results = await prisma.hanzi.findMany({ where: { forms: { some: { transcriptions: { some: { value: { contains: "hǎo" }, }, }, }, }, }, include: { forms: { where: { isDefault: true }, include: { transcriptions: { where: { type: "pinyin" }, take: 1 }, }, take: 1, }, }, }) expect(results.length).toBeGreaterThan(0) expect(results.some((h) => h.simplified === "好")).toBe(true) }) it("should search hanzi by meaning", async () => { const results = await prisma.hanzi.findMany({ where: { forms: { some: { meanings: { some: { meaning: { contains: "good" }, }, }, }, }, }, include: { forms: { where: { isDefault: true }, include: { meanings: { orderBy: { orderIndex: "asc" }, take: 1 }, }, take: 1, }, }, }) expect(results.length).toBeGreaterThan(0) expect(results.some((h) => h.simplified === "好")).toBe(true) }) it("should support multi-character hanzi in database", async () => { const multiChar = await prisma.hanzi.findUnique({ where: { simplified: "中国" }, include: { forms: { where: { isDefault: true }, include: { transcriptions: true, meanings: true, }, take: 1, }, }, }) expect(multiChar).toBeDefined() expect(multiChar?.simplified).toBe("中国") expect(multiChar?.forms[0].traditional).toBe("中國") expect(multiChar?.forms[0].transcriptions[0].value).toBe("zhōng guó") expect(multiChar?.forms[0].meanings[0].meaning).toBe("China") }) }) describe("Hanzi List Parsing Logic", () => { beforeEach(async () => { // Create test hanzi await prisma.hanzi.createMany({ data: [ { simplified: "好", radical: "女", frequency: 100 }, { simplified: "爱", radical: "爫", frequency: 200 }, { simplified: "你", radical: "人", frequency: 50 }, { simplified: "中国", radical: null, frequency: 300 }, ], }) }) it("should parse newline-separated list", () => { const input = "好\n爱\n你" const parsed = input .split(/[\n,\s]+/) .map((s) => s.trim()) .filter((s) => s.length > 0) expect(parsed).toEqual(["好", "爱", "你"]) }) it("should parse comma-separated list", () => { const input = "好, 爱, 你" const parsed = input .split(/[\n,\s]+/) .map((s) => s.trim()) .filter((s) => s.length > 0) expect(parsed).toEqual(["好", "爱", "你"]) }) it("should parse space-separated list", () => { const input = "好 爱 你" const parsed = input .split(/[\n,\s]+/) .map((s) => s.trim()) .filter((s) => s.length > 0) expect(parsed).toEqual(["好", "爱", "你"]) }) it("should handle multi-character words", () => { const input = "好 中国 你" const parsed = input .split(/[\n,\s]+/) .map((s) => s.trim()) .filter((s) => s.length > 0) expect(parsed).toEqual(["好", "中国", "你"]) }) it("should detect duplicates", () => { const input = "好 爱 好 你" const chars = input .split(/[\n,\s]+/) .map((s) => s.trim()) .filter((s) => s.length > 0) const seen = new Set() const duplicates: string[] = [] const unique = chars.filter((char) => { if (seen.has(char)) { if (!duplicates.includes(char)) { duplicates.push(char) } return false } seen.add(char) return true }) expect(unique).toEqual(["好", "爱", "你"]) expect(duplicates).toEqual(["好"]) }) it("should validate all hanzi exist (strict mode)", async () => { const input = ["好", "不存在", "你"] const foundHanzi = await prisma.hanzi.findMany({ where: { simplified: { in: input }, }, }) const foundSet = new Set(foundHanzi.map((h) => h.simplified)) const notFound = input.filter((char) => !foundSet.has(char)) expect(notFound).toEqual(["不存在"]) }) it("should preserve order from input", async () => { const input = ["你", "爱", "好"] const foundHanzi = await prisma.hanzi.findMany({ where: { simplified: { in: input }, }, }) // Create a map for quick lookup const hanziMap = new Map(foundHanzi.map((h) => [h.simplified, h])) // Preserve input order const ordered = input .map((char) => hanziMap.get(char)) .filter((h): h is NonNullable => h !== undefined) expect(ordered[0].simplified).toBe("你") expect(ordered[1].simplified).toBe("爱") expect(ordered[2].simplified).toBe("好") }) }) })