Files
metabuilder/frontends/pastebin/tests/unit/hooks/useDatabaseOperations.test.ts
2026-03-09 22:30:41 +00:00

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();
});
});
});