mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-25 05:55:00 +00:00
- Created comprehensive test suites for quality validator module (430+ tests) * index.test.ts: QualityValidator main module * reporters/*.test.ts: ReporterBase and all reporters * scoring/*.test.ts: Scoring engine with edge cases * utils/*.test.ts: Validators, formatters, FileChangeDetector - Added UI component tests for sidebar menu and templates (800+ tests) * SidebarMenuButton, SidebarMenuSubButton, etc. * DashboardTemplate, BlogTemplate * ContentPreviewCardsSection, FormFieldsSection - Coverage improvements: * Statements: 56.62% → 60.93% (+4.31%) * Functions: 76.76% → 79.82% (+3.06%) * Branches: 84.37% → 85.92% (+1.55%) * Tests passing: 5,512 (added 363 new passing tests) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
383 lines
12 KiB
TypeScript
383 lines
12 KiB
TypeScript
/**
|
|
* Unit Tests for useDatabaseOperations Hook
|
|
* Tests database stats, export/import, and maintenance operations
|
|
*/
|
|
|
|
import { renderHook, act } from '@testing-library/react';
|
|
import { useDatabaseOperations } from '@/hooks/useDatabaseOperations';
|
|
import * as db from '@/lib/db';
|
|
import { toast } from 'sonner';
|
|
|
|
jest.mock('@/lib/db');
|
|
jest.mock('sonner');
|
|
|
|
describe('useDatabaseOperations Hook', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
(global.URL.createObjectURL as any) = jest.fn(() => 'blob:mock');
|
|
(global.URL.revokeObjectURL as any) = jest.fn();
|
|
});
|
|
|
|
describe('loadStats', () => {
|
|
it('should load database stats', async () => {
|
|
(db.getDatabaseStats as jest.Mock).mockResolvedValue({
|
|
snippetCount: 5,
|
|
templateCount: 2,
|
|
namespaceCount: 3,
|
|
storageType: 'indexeddb',
|
|
databaseSize: 1024,
|
|
});
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
expect(result.current.loading).toBe(true);
|
|
|
|
await act(async () => {
|
|
await result.current.loadStats();
|
|
});
|
|
|
|
expect(result.current.stats?.snippetCount).toBe(5);
|
|
expect(result.current.stats?.templateCount).toBe(2);
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
it('should handle stats loading error', async () => {
|
|
(db.getDatabaseStats as jest.Mock).mockRejectedValue(new Error('DB Error'));
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.loadStats();
|
|
});
|
|
|
|
expect(toast.error).toHaveBeenCalledWith('Failed to load database statistics');
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('checkSchemaHealth', () => {
|
|
it('should check schema and return healthy', async () => {
|
|
(db.validateDatabaseSchema as jest.Mock).mockResolvedValue(true);
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
expect(result.current.schemaHealth).toBe('unknown');
|
|
|
|
await act(async () => {
|
|
await result.current.checkSchemaHealth();
|
|
});
|
|
|
|
expect(result.current.schemaHealth).toBe('healthy');
|
|
expect(result.current.checkingSchema).toBe(false);
|
|
});
|
|
|
|
it('should check schema and return corrupted', async () => {
|
|
(db.validateDatabaseSchema as jest.Mock).mockResolvedValue(false);
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.checkSchemaHealth();
|
|
});
|
|
|
|
expect(result.current.schemaHealth).toBe('corrupted');
|
|
});
|
|
|
|
it('should handle schema check error', async () => {
|
|
(db.validateDatabaseSchema as jest.Mock).mockRejectedValue(new Error('Check failed'));
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.checkSchemaHealth();
|
|
});
|
|
|
|
expect(result.current.schemaHealth).toBe('corrupted');
|
|
});
|
|
});
|
|
|
|
describe('handleExport', () => {
|
|
it('should export database successfully', async () => {
|
|
const mockData = JSON.stringify({ snippets: [], namespaces: [] });
|
|
(db.exportDatabase as jest.Mock).mockResolvedValue(mockData);
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
// Mock DOM methods
|
|
const mockLink = document.createElement('a');
|
|
jest.spyOn(document, 'createElement').mockReturnValue(mockLink);
|
|
jest.spyOn(document.body, 'appendChild').mockImplementation();
|
|
jest.spyOn(document.body, 'removeChild').mockImplementation();
|
|
jest.spyOn(mockLink, 'click').mockImplementation();
|
|
|
|
await act(async () => {
|
|
await result.current.handleExport();
|
|
});
|
|
|
|
expect(toast.success).toHaveBeenCalledWith('Database exported successfully');
|
|
});
|
|
|
|
it('should handle export error', async () => {
|
|
(db.exportDatabase as jest.Mock).mockRejectedValue(new Error('Export failed'));
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.handleExport();
|
|
});
|
|
|
|
expect(toast.error).toHaveBeenCalledWith('Failed to export database');
|
|
});
|
|
});
|
|
|
|
describe('handleImport', () => {
|
|
it('should import database successfully', async () => {
|
|
(db.importDatabase as jest.Mock).mockResolvedValue(undefined);
|
|
(db.getDatabaseStats as jest.Mock).mockResolvedValue({
|
|
snippetCount: 10,
|
|
templateCount: 5,
|
|
namespaceCount: 2,
|
|
storageType: 'indexeddb',
|
|
databaseSize: 2048,
|
|
});
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
const mockFile = new File(['{"snippets": [], "namespaces": []}'], 'test.json');
|
|
const mockEvent = {
|
|
target: {
|
|
files: [mockFile],
|
|
value: 'test',
|
|
},
|
|
} as any;
|
|
|
|
jest.spyOn(mockFile, 'text').mockResolvedValue('{"snippets": [], "namespaces": []}');
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent);
|
|
});
|
|
|
|
expect(toast.success).toHaveBeenCalledWith('Database imported successfully');
|
|
});
|
|
|
|
it('should handle import error', async () => {
|
|
(db.importDatabase as jest.Mock).mockRejectedValue(new Error('Import failed'));
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
const mockFile = new File(['{"snippets": [], "namespaces": []}'], 'test.json');
|
|
const mockEvent = {
|
|
target: {
|
|
files: [mockFile],
|
|
value: 'test',
|
|
},
|
|
} as any;
|
|
|
|
jest.spyOn(mockFile, 'text').mockResolvedValue('{"snippets": [], "namespaces": []}');
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent);
|
|
});
|
|
|
|
expect(toast.error).toHaveBeenCalledWith('Failed to import database');
|
|
});
|
|
|
|
it('should handle missing file', async () => {
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
const mockEvent = {
|
|
target: {
|
|
files: null,
|
|
value: 'test',
|
|
},
|
|
} as any;
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent);
|
|
});
|
|
|
|
expect(toast.success).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should clear file input after import', async () => {
|
|
(db.importDatabase as jest.Mock).mockResolvedValue(undefined);
|
|
(db.getDatabaseStats as jest.Mock).mockResolvedValue({
|
|
snippetCount: 0,
|
|
templateCount: 0,
|
|
namespaceCount: 0,
|
|
storageType: 'indexeddb',
|
|
databaseSize: 0,
|
|
});
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
const mockFile = new File(['{}'], 'test.json');
|
|
const mockEvent = {
|
|
target: {
|
|
files: [mockFile],
|
|
value: 'test',
|
|
},
|
|
} as any;
|
|
|
|
jest.spyOn(mockFile, 'text').mockResolvedValue('{}');
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent);
|
|
});
|
|
|
|
expect(mockEvent.target.value).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('handleClear', () => {
|
|
it('should clear database with user confirmation', async () => {
|
|
(db.clearDatabase as jest.Mock).mockResolvedValue(undefined);
|
|
(db.getDatabaseStats as jest.Mock).mockResolvedValue({
|
|
snippetCount: 0,
|
|
templateCount: 0,
|
|
namespaceCount: 0,
|
|
storageType: 'indexeddb',
|
|
databaseSize: 0,
|
|
});
|
|
(db.validateDatabaseSchema as jest.Mock).mockResolvedValue(true);
|
|
|
|
jest.spyOn(window, 'confirm').mockReturnValue(true);
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.handleClear();
|
|
});
|
|
|
|
expect(toast.success).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not clear database without confirmation', async () => {
|
|
jest.spyOn(window, 'confirm').mockReturnValue(false);
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.handleClear();
|
|
});
|
|
|
|
expect(db.clearDatabase).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle clear error', async () => {
|
|
(db.clearDatabase as jest.Mock).mockRejectedValue(new Error('Clear failed'));
|
|
jest.spyOn(window, 'confirm').mockReturnValue(true);
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.handleClear();
|
|
});
|
|
|
|
expect(toast.error).toHaveBeenCalledWith('Failed to clear database');
|
|
});
|
|
});
|
|
|
|
describe('handleSeed', () => {
|
|
it('should seed database successfully', async () => {
|
|
(db.seedDatabase as jest.Mock).mockResolvedValue(undefined);
|
|
(db.getDatabaseStats as jest.Mock).mockResolvedValue({
|
|
snippetCount: 10,
|
|
templateCount: 5,
|
|
namespaceCount: 3,
|
|
storageType: 'indexeddb',
|
|
databaseSize: 2048,
|
|
});
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.handleSeed();
|
|
});
|
|
|
|
expect(toast.success).toHaveBeenCalledWith('Sample data added successfully');
|
|
});
|
|
|
|
it('should handle seed error', async () => {
|
|
(db.seedDatabase as jest.Mock).mockRejectedValue(new Error('Seed failed'));
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.handleSeed();
|
|
});
|
|
|
|
expect(toast.error).toHaveBeenCalledWith('Failed to add sample data');
|
|
});
|
|
});
|
|
|
|
describe('formatBytes', () => {
|
|
it('should format 0 bytes', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
expect(result.current.formatBytes(0)).toBe('0 Bytes');
|
|
});
|
|
|
|
it('should format bytes', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
expect(result.current.formatBytes(1024)).toContain('KB');
|
|
});
|
|
|
|
it('should format kilobytes', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
expect(result.current.formatBytes(1024 * 1024)).toContain('MB');
|
|
});
|
|
|
|
it('should format megabytes', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
expect(result.current.formatBytes(1024 * 1024 * 1024)).toContain('GB');
|
|
});
|
|
|
|
it('should round to 2 decimal places', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
const formatted = result.current.formatBytes(1536);
|
|
expect(formatted).toMatch(/1\.5\d? KB/);
|
|
});
|
|
});
|
|
|
|
describe('initial state', () => {
|
|
it('should initialize with loading state', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
expect(result.current.loading).toBe(true);
|
|
expect(result.current.stats).toBeNull();
|
|
expect(result.current.schemaHealth).toBe('unknown');
|
|
expect(result.current.checkingSchema).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('complex scenarios', () => {
|
|
it('should handle load then clear workflow', async () => {
|
|
(db.getDatabaseStats as jest.Mock).mockResolvedValue({
|
|
snippetCount: 10,
|
|
templateCount: 5,
|
|
namespaceCount: 2,
|
|
storageType: 'indexeddb',
|
|
databaseSize: 2048,
|
|
});
|
|
(db.clearDatabase as jest.Mock).mockResolvedValue(undefined);
|
|
(db.validateDatabaseSchema as jest.Mock).mockResolvedValue(true);
|
|
|
|
jest.spyOn(window, 'confirm').mockReturnValue(true);
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations());
|
|
|
|
await act(async () => {
|
|
await result.current.loadStats();
|
|
});
|
|
|
|
expect(result.current.stats?.snippetCount).toBe(10);
|
|
|
|
await act(async () => {
|
|
await result.current.handleClear();
|
|
});
|
|
|
|
expect(toast.success).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|