mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-27 07:14:56 +00:00
Full-featured pastebin application with: - Next.js frontend with TypeScript - Express backend with SQLite/PostgreSQL - Syntax highlighting for 100+ languages - Code quality validation system - Comprehensive accessibility (WCAG compliance) - Docker deployment configuration - Playwright E2E tests - Jest unit tests This provides a standalone web application that can be integrated as a capability module in the Universal Platform. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
353 lines
11 KiB
TypeScript
353 lines
11 KiB
TypeScript
import { renderHook, act } from '@testing-library/react'
|
|
import { useDatabaseOperations } from './useDatabaseOperations'
|
|
import * as dbModule from '@/lib/db'
|
|
import * as sonerModule from 'sonner'
|
|
|
|
// Mock the database module
|
|
jest.mock('@/lib/db')
|
|
|
|
// Mock sonner toast
|
|
jest.mock('sonner', () => ({
|
|
toast: {
|
|
success: jest.fn(),
|
|
error: jest.fn(),
|
|
},
|
|
}))
|
|
|
|
const mockDb = dbModule as jest.Mocked<typeof dbModule>
|
|
const mockToast = sonerModule.toast as jest.Mocked<typeof sonerModule.toast>
|
|
|
|
describe('useDatabaseOperations Hook', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe('loadStats', () => {
|
|
it('should load database stats successfully', async () => {
|
|
const mockStats = {
|
|
snippetCount: 10,
|
|
templateCount: 5,
|
|
namespaceCount: 2,
|
|
storageType: 'indexeddb' as const,
|
|
databaseSize: 1024,
|
|
}
|
|
mockDb.getDatabaseStats.mockResolvedValueOnce(mockStats)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
expect(result.current.loading).toBe(true)
|
|
|
|
await act(async () => {
|
|
await result.current.loadStats()
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.stats).toEqual(mockStats)
|
|
expect(mockDb.getDatabaseStats).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should handle errors when loading stats', async () => {
|
|
const error = new Error('Database error')
|
|
mockDb.getDatabaseStats.mockRejectedValueOnce(error)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.loadStats()
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
expect(result.current.stats).toBeNull()
|
|
expect(mockToast.error).toHaveBeenCalledWith('Failed to load database statistics')
|
|
})
|
|
})
|
|
|
|
describe('checkSchemaHealth', () => {
|
|
it('should check schema health successfully', async () => {
|
|
mockDb.validateDatabaseSchema.mockResolvedValueOnce(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 set corrupted state when schema is invalid', async () => {
|
|
mockDb.validateDatabaseSchema.mockResolvedValueOnce(false)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.checkSchemaHealth()
|
|
})
|
|
|
|
expect(result.current.schemaHealth).toBe('corrupted')
|
|
})
|
|
|
|
it('should handle errors during schema check', async () => {
|
|
const error = new Error('Schema check failed')
|
|
mockDb.validateDatabaseSchema.mockRejectedValueOnce(error)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.checkSchemaHealth()
|
|
})
|
|
|
|
expect(result.current.schemaHealth).toBe('corrupted')
|
|
expect(result.current.checkingSchema).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('handleExport', () => {
|
|
it('should export database successfully', async () => {
|
|
const mockJsonData = '{"data": "test"}'
|
|
mockDb.exportDatabase.mockResolvedValueOnce(mockJsonData)
|
|
|
|
// Mock DOM methods
|
|
const mockUrl = 'blob:http://test'
|
|
const mockLink = document.createElement('a')
|
|
|
|
global.URL.createObjectURL = jest.fn(() => mockUrl)
|
|
global.URL.revokeObjectURL = jest.fn()
|
|
jest.spyOn(document, 'createElement').mockReturnValueOnce(mockLink)
|
|
jest.spyOn(document.body, 'appendChild').mockReturnValueOnce(mockLink)
|
|
jest.spyOn(document.body, 'removeChild').mockReturnValueOnce(mockLink)
|
|
jest.spyOn(mockLink, 'click').mockReturnValueOnce()
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.handleExport()
|
|
})
|
|
|
|
expect(mockDb.exportDatabase).toHaveBeenCalledTimes(1)
|
|
expect(mockToast.success).toHaveBeenCalledWith('Database exported successfully')
|
|
})
|
|
|
|
it('should handle export errors', async () => {
|
|
const error = new Error('Export failed')
|
|
mockDb.exportDatabase.mockRejectedValueOnce(error)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.handleExport()
|
|
})
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith('Failed to export database')
|
|
})
|
|
})
|
|
|
|
describe('handleImport', () => {
|
|
it('should handle no file selected', async () => {
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
const mockEvent = {
|
|
target: {
|
|
files: null,
|
|
value: '',
|
|
},
|
|
} as unknown as React.ChangeEvent<HTMLInputElement>
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent)
|
|
})
|
|
|
|
expect(mockDb.importDatabase).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should import database successfully', async () => {
|
|
mockDb.importDatabase.mockResolvedValueOnce(undefined)
|
|
const mockStats = {
|
|
snippetCount: 20,
|
|
templateCount: 10,
|
|
namespaceCount: 3,
|
|
storageType: 'indexeddb' as const,
|
|
databaseSize: 2048,
|
|
}
|
|
mockDb.getDatabaseStats.mockResolvedValueOnce(mockStats)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
const jsonData = JSON.stringify({ data: 'test' })
|
|
const mockFile = new File([jsonData], 'backup.json', { type: 'application/json' })
|
|
|
|
// Mock the File.text() method which returns a Promise
|
|
mockFile.text = jest.fn().mockResolvedValueOnce(jsonData)
|
|
|
|
const mockEvent = {
|
|
target: {
|
|
files: [mockFile],
|
|
value: 'backup.json',
|
|
},
|
|
} as unknown as React.ChangeEvent<HTMLInputElement>
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent)
|
|
})
|
|
|
|
expect(mockDb.importDatabase).toHaveBeenCalled()
|
|
expect(mockToast.success).toHaveBeenCalledWith('Database imported successfully')
|
|
})
|
|
|
|
it('should clear file input after import', async () => {
|
|
mockDb.importDatabase.mockResolvedValueOnce(undefined)
|
|
mockDb.getDatabaseStats.mockResolvedValueOnce({
|
|
snippetCount: 0,
|
|
templateCount: 0,
|
|
namespaceCount: 0,
|
|
storageType: 'indexeddb' as const,
|
|
databaseSize: 0,
|
|
})
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
const mockEvent = {
|
|
target: {
|
|
files: [new File([], 'test.json')],
|
|
value: 'test.json',
|
|
},
|
|
} as unknown as React.ChangeEvent<HTMLInputElement>
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent)
|
|
})
|
|
|
|
expect(mockEvent.target.value).toBe('')
|
|
})
|
|
|
|
it('should handle import errors', async () => {
|
|
const error = new Error('Import failed')
|
|
mockDb.importDatabase.mockRejectedValueOnce(error)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
const mockEvent = {
|
|
target: {
|
|
files: [new File([], 'test.json')],
|
|
value: 'test.json',
|
|
},
|
|
} as unknown as React.ChangeEvent<HTMLInputElement>
|
|
|
|
await act(async () => {
|
|
await result.current.handleImport(mockEvent)
|
|
})
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith('Failed to import database')
|
|
})
|
|
})
|
|
|
|
describe('handleClear', () => {
|
|
beforeEach(() => {
|
|
global.confirm = jest.fn(() => true)
|
|
})
|
|
|
|
it('should clear database when confirmed', async () => {
|
|
mockDb.clearDatabase.mockResolvedValueOnce(undefined)
|
|
mockDb.getDatabaseStats.mockResolvedValueOnce({
|
|
snippetCount: 0,
|
|
templateCount: 0,
|
|
namespaceCount: 0,
|
|
storageType: 'indexeddb' as const,
|
|
databaseSize: 0,
|
|
})
|
|
mockDb.validateDatabaseSchema.mockResolvedValueOnce(true)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.handleClear()
|
|
})
|
|
|
|
expect(global.confirm).toHaveBeenCalled()
|
|
expect(mockDb.clearDatabase).toHaveBeenCalledTimes(1)
|
|
expect(mockToast.success).toHaveBeenCalledWith('Database cleared and schema recreated successfully')
|
|
})
|
|
|
|
it('should not clear database when not confirmed', async () => {
|
|
global.confirm = jest.fn(() => false)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.handleClear()
|
|
})
|
|
|
|
expect(mockDb.clearDatabase).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle clear errors', async () => {
|
|
const error = new Error('Clear failed')
|
|
mockDb.clearDatabase.mockRejectedValueOnce(error)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.handleClear()
|
|
})
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith('Failed to clear database')
|
|
})
|
|
})
|
|
|
|
describe('handleSeed', () => {
|
|
it('should seed database successfully', async () => {
|
|
mockDb.seedDatabase.mockResolvedValueOnce(undefined)
|
|
mockDb.getDatabaseStats.mockResolvedValueOnce({
|
|
snippetCount: 5,
|
|
templateCount: 3,
|
|
namespaceCount: 1,
|
|
storageType: 'indexeddb' as const,
|
|
databaseSize: 512,
|
|
})
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.handleSeed()
|
|
})
|
|
|
|
expect(mockDb.seedDatabase).toHaveBeenCalledTimes(1)
|
|
expect(mockToast.success).toHaveBeenCalledWith('Sample data added successfully')
|
|
})
|
|
|
|
it('should handle seed errors', async () => {
|
|
const error = new Error('Seed failed')
|
|
mockDb.seedDatabase.mockRejectedValueOnce(error)
|
|
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
await act(async () => {
|
|
await result.current.handleSeed()
|
|
})
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith('Failed to add sample data')
|
|
})
|
|
})
|
|
|
|
describe('formatBytes', () => {
|
|
it('should format bytes correctly', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
expect(result.current.formatBytes(0)).toBe('0 Bytes')
|
|
expect(result.current.formatBytes(1024)).toBe('1 KB')
|
|
expect(result.current.formatBytes(1048576)).toBe('1 MB')
|
|
expect(result.current.formatBytes(1073741824)).toBe('1 GB')
|
|
})
|
|
|
|
it('should handle decimal values', () => {
|
|
const { result } = renderHook(() => useDatabaseOperations())
|
|
|
|
const formatted = result.current.formatBytes(1536) // 1.5 KB
|
|
expect(formatted).toMatch(/1\.5\s*KB/)
|
|
})
|
|
})
|
|
})
|