diff --git a/src/lib/components/QrScanner.svelte b/src/lib/components/QrScanner.svelte index ca6d3e0..12fa3dd 100644 --- a/src/lib/components/QrScanner.svelte +++ b/src/lib/components/QrScanner.svelte @@ -36,11 +36,14 @@ scanning = false; }, (err) => { - notifications.error(err); + if ((err as unknown as Error).toString().includes('NotFoundException')) { + return; + } + notifications.error(`error during scanning: ${err}`); } ); } catch (err) { - notifications.error(`Error starting camera: ${err}`); + notifications.error(`error during starting camera: ${err}`); scanning = false; } } @@ -53,7 +56,15 @@ const result = await scanner.scanFile(file, true); onScan(result); } catch (err) { - notifications.error(`Error scanning file: ${err}`); + if ((err as unknown as Error).toString().includes('NotFoundException')) { + notifications.error( + 'Unable to find QR code in the image. Please ensure the image is clear and contains a valid QR code.' + ); + } else { + notifications.error(`error during scanning file: ${err}`); + } + } finally { + fileInput.value = ''; } } diff --git a/tests/firebase.test.ts b/tests/firebase.test.ts new file mode 100644 index 0000000..368c913 --- /dev/null +++ b/tests/firebase.test.ts @@ -0,0 +1,166 @@ +import type { CollectionReference, DocumentReference } from 'firebase-admin/firestore'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + adminDb, + checkRemoveParticipantPermission, + getGroupsData, + getSessionData +} from '../src/lib/server/firebase'; + +// Mock Firestore +vi.mock('firebase-admin/firestore', () => ({ + getFirestore: vi.fn(() => ({ + collection: vi.fn(), + doc: vi.fn() + })), + Timestamp: { + now: vi.fn(() => ({ toMillis: () => 1234567890000 })) + } +})); + +describe('Firebase Server Functions', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('getGroupsData', () => { + it('應該正確返回群組資料', async () => { + const mockGroups = { + empty: false, + docs: [ + { + data: () => ({ + number: 1, + participants: ['user1', 'user2'], + concept: null + }) + }, + { + data: () => ({ + number: 2, + participants: ['user3', 'user4'], + concept: null + }) + } + ] + }; + + const groupsRef = { + get: vi.fn().mockResolvedValue(mockGroups) + } as unknown as CollectionReference; + + const result = await getGroupsData(groupsRef); + expect(result).toHaveLength(2); + expect(result[0].number).toBe(1); + expect(result[1].participants).toContain('user3'); + }); + + it('當沒有找到群組時應該拋出404錯誤', async () => { + const groupsRef = { + get: vi.fn().mockResolvedValue({ empty: true }) + } as unknown as CollectionReference; + + await expect(getGroupsData(groupsRef)).rejects.toThrow('Groups not found'); + }); + }); + + describe('getSessionData', () => { + it('應該正確返回會話資料', async () => { + const mockSession = { + exists: true, + data: () => ({ + title: 'Test Session', + host: 'user1', + status: 'preparing' + }) + }; + + const sessionRef = { + get: vi.fn().mockResolvedValue(mockSession) + } as unknown as DocumentReference; + + const result = await getSessionData(sessionRef); + expect(result.title).toBe('Test Session'); + expect(result.host).toBe('user1'); + }); + + it('當沒有找到會話時應該拋出404錯誤', async () => { + const sessionRef = { + get: vi.fn().mockResolvedValue({ exists: false }) + } as unknown as DocumentReference; + + await expect(getSessionData(sessionRef)).rejects.toThrow('Session not found'); + }); + }); + + describe('checkRemoveParticipantPermission', () => { + it('當使用者是主持人時應該返回true', async () => { + const mockSession = { + exists: true, + data: () => ({ + host: 'host123', + status: 'preparing' + }) + }; + + vi.spyOn(adminDb, 'collection').mockReturnValue({ + doc: vi.fn().mockReturnValue({ + get: vi.fn().mockResolvedValue(mockSession) + }) + } as unknown as CollectionReference); + + const result = await checkRemoveParticipantPermission( + 'session123', + 'host123', + 'participant123' + ); + expect(result).toBe(true); + }); + + it('當使用者是被移除的參與者時應該返回true', async () => { + const mockSession = { + exists: true, + data: () => ({ + host: 'host123', + status: 'preparing' + }) + }; + + vi.spyOn(adminDb, 'collection').mockReturnValue({ + doc: vi.fn().mockReturnValue({ + get: vi.fn().mockResolvedValue(mockSession) + }) + } as unknown as CollectionReference); + + const result = await checkRemoveParticipantPermission( + 'session123', + 'participant123', + 'participant123' + ); + expect(result).toBe(true); + }); + + it('當使用者既不是主持人也不是被移除的參與者時應該返回false', async () => { + const mockSession = { + exists: true, + data: () => ({ + host: 'host123', + status: 'preparing' + }) + }; + + vi.spyOn(adminDb, 'collection').mockReturnValue({ + doc: vi.fn().mockReturnValue({ + get: vi.fn().mockResolvedValue(mockSession) + }) + } as unknown as CollectionReference); + + const result = await checkRemoveParticipantPermission( + 'session123', + 'otherUser123', + 'participant123' + ); + expect(result).toBe(false); + }); + }); +}); diff --git a/tests/getUser.test.ts b/tests/getUser.test.ts new file mode 100644 index 0000000..bddd7e8 --- /dev/null +++ b/tests/getUser.test.ts @@ -0,0 +1,79 @@ +import type { DocumentReference, DocumentSnapshot } from 'firebase/firestore'; +import { doc, getDoc } from 'firebase/firestore'; +import type { Mock } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { getUser } from '../src/lib/utils/getUser'; + +// Mock Firebase +vi.mock('firebase/firestore', () => ({ + doc: vi.fn(), + getDoc: vi.fn() +})); + +vi.mock('$lib/firebase', () => ({ + db: {} +})); + +describe('getUser', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('應該正確返回使用者資料', async () => { + const mockUserData = { + uid: 'test122', + displayName: 'Test User', + title: 'Developer', + bio: 'Hello World' + }; + + (doc as unknown as Mock).mockReturnValue({} as DocumentReference); + (getDoc as unknown as Mock).mockResolvedValue({ + exists: () => true, + data: () => mockUserData + } as unknown as DocumentSnapshot); + + const user = await getUser('test123'); + expect(user).toEqual(mockUserData); + }); + + it('當使用者不存在時應該返回預設資料', async () => { + const uid = 'nonexistent123'; + (doc as unknown as Mock).mockReturnValue({} as DocumentReference); + (getDoc as unknown as Mock).mockResolvedValue({ + exists: () => false, + data: () => null + } as unknown as DocumentSnapshot); + + const user = await getUser(uid); + expect(user).toEqual({ + uid, + displayName: uid, + title: null, + bio: null + }); + }); + + it('應該快取使用者資料', async () => { + const mockUserData = { + uid: 'test122', + displayName: 'Test User', + title: 'Developer', + bio: 'Hello World' + }; + + (doc as unknown as Mock).mockReturnValue({} as DocumentReference); + (getDoc as unknown as Mock).mockResolvedValue({ + exists: () => true, + data: () => mockUserData + } as unknown as DocumentSnapshot); + + // 第一次呼叫 + await getUser('test122'); + // 第二次呼叫 + await getUser('test122'); + + // 應該只呼叫一次 getDoc + expect(getDoc).toHaveBeenCalledTimes(1); + }); +});