diff --git a/frontend/src/__tests__/dms/BulkActions.test.tsx b/frontend/src/__tests__/dms/BulkActions.test.tsx new file mode 100644 index 0000000..81a83f7 --- /dev/null +++ b/frontend/src/__tests__/dms/BulkActions.test.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { BulkActions } from '@/components/dms/BulkActions'; +import type { DmsFolder } from '@/api/dms'; + +vi.mock('@/api/dms', () => ({ + bulkMoveFiles: vi.fn().mockResolvedValue(undefined), + bulkDeleteFiles: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock('@/components/ui/Toast', () => ({ + useToast: () => ({ success: vi.fn(), error: vi.fn(), info: vi.fn() }), +})); + +const mockFolders: DmsFolder[] = [ + { id: 'folder1', name: 'Documents', parent_id: null, created_by: 'user1', created_at: '2024-01-01T00:00:00Z' }, + { id: 'folder2', name: 'Images', parent_id: null, created_by: 'user1', created_at: '2024-01-01T00:00:00Z' }, +]; + +const defaultProps = { + selectedFileIds: new Set(['f1', 'f2', 'f3']), + folders: mockFolders, + onClear: vi.fn(), + onActionComplete: vi.fn(), +}; + +describe('BulkActions', () => { + it('renders bulk actions bar when files are selected', () => { + render(); + expect(screen.getByTestId('bulk-actions-bar')).toBeInTheDocument(); + }); + + it('does not render when no files selected', () => { + render(); + expect(screen.queryByTestId('bulk-actions-bar')).not.toBeInTheDocument(); + }); + + it('renders selected file count', () => { + render(); + expect(screen.getByText(/3/)).toBeInTheDocument(); + }); + + it('renders move button', () => { + render(); + expect(screen.getByText(/move|verschieben/i)).toBeInTheDocument(); + }); + + it('renders delete button', () => { + render(); + expect(screen.getByText(/delete|löschen/i)).toBeInTheDocument(); + }); + + it('renders cancel/clear button', () => { + render(); + expect(screen.getByText(/cancel|abbrechen/i)).toBeInTheDocument(); + }); + + it('calls onClear when cancel button clicked', () => { + render(); + fireEvent.click(screen.getByText(/cancel|abbrechen/i)); + expect(defaultProps.onClear).toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/__tests__/dms/FileDetails.test.tsx b/frontend/src/__tests__/dms/FileDetails.test.tsx new file mode 100644 index 0000000..4780325 --- /dev/null +++ b/frontend/src/__tests__/dms/FileDetails.test.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { FileDetails } from '@/components/dms/FileDetails'; +import type { DmsFile } from '@/api/dms'; + +const mockFile: DmsFile = { + id: 'f1', + folder_id: 'folder1', + name: 'document.pdf', + mime_type: 'application/pdf', + size_bytes: 1024, + storage_path: '/path/f1', + deleted_at: null, + uploaded_by: 'user1', + created_at: '2024-01-01T10:00:00Z', + updated_at: '2024-01-02T12:00:00Z', + access_level: 'read', +}; + +const defaultProps = { + file: mockFile, + onPreview: vi.fn(), + onShare: vi.fn(), + onDelete: vi.fn(), + onClose: vi.fn(), +}; + +describe('FileDetails', () => { + it('renders file details container', () => { + render(); + expect(screen.getByTestId('file-details')).toBeInTheDocument(); + }); + + it('renders file name in header', () => { + render(); + expect(screen.getAllByText('document.pdf').length).toBeGreaterThan(0); + }); + + it('renders empty state when no file selected', () => { + render(); + expect(screen.getByTestId('file-details-empty')).toBeInTheDocument(); + }); + + it('renders close button', () => { + render(); + expect(screen.getByLabelText(/close|schließen/i)).toBeInTheDocument(); + }); + + it('calls onClose when close button clicked', () => { + render(); + fireEvent.click(screen.getByLabelText(/close|schließen/i)); + expect(defaultProps.onClose).toHaveBeenCalled(); + }); + + it('renders preview button', () => { + render(); + const previewBtn = screen.getByText(/preview|vorschau/i); + expect(previewBtn).toBeInTheDocument(); + }); + + it('calls onPreview when preview button clicked', () => { + render(); + fireEvent.click(screen.getByText(/preview|vorschau/i)); + expect(defaultProps.onPreview).toHaveBeenCalledWith(mockFile); + }); + + it('renders delete button', () => { + render(); + expect(screen.getByText(/delete|löschen/i)).toBeInTheDocument(); + }); + + it('calls onDelete when delete button clicked', () => { + render(); + fireEvent.click(screen.getByText(/delete|löschen/i)); + expect(defaultProps.onDelete).toHaveBeenCalledWith(mockFile); + }); +}); diff --git a/frontend/src/__tests__/dms/FileExplorer.test.tsx b/frontend/src/__tests__/dms/FileExplorer.test.tsx new file mode 100644 index 0000000..0c3e43d --- /dev/null +++ b/frontend/src/__tests__/dms/FileExplorer.test.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { FileExplorer } from '@/components/dms/FileExplorer'; +import type { DmsFile } from '@/api/dms'; + +const mockFiles: DmsFile[] = [ + { id: 'f1', folder_id: null, name: 'document.pdf', mime_type: 'application/pdf', size_bytes: 1024, storage_path: '/path/f1', deleted_at: null, uploaded_by: 'user1', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-02T00:00:00Z' }, + { id: 'f2', folder_id: null, name: 'image.png', mime_type: 'image/png', size_bytes: 2048, storage_path: '/path/f2', deleted_at: null, uploaded_by: 'user2', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-03T00:00:00Z' }, +]; + +const defaultProps = { + files: mockFiles, + selectedFileIds: new Set(), + selectedFile: null, + viewMode: 'list' as const, + onToggleSelect: vi.fn(), + onFileClick: vi.fn(), + onFileDoubleClick: vi.fn(), + onFileShare: vi.fn(), + onFileDelete: vi.fn(), + onFilePreview: vi.fn(), + sortBy: 'name' as const, + sortOrder: 'asc' as const, + onSortChange: vi.fn(), +}; + +describe('FileExplorer', () => { + it('renders file list in list view', () => { + render(); + expect(screen.getByTestId('file-explorer-list')).toBeInTheDocument(); + }); + + it('renders file rows for each file', () => { + render(); + expect(screen.getByTestId('file-row-f1')).toBeInTheDocument(); + expect(screen.getByTestId('file-row-f2')).toBeInTheDocument(); + }); + + it('renders file names', () => { + render(); + expect(screen.getByText('document.pdf')).toBeInTheDocument(); + expect(screen.getByText('image.png')).toBeInTheDocument(); + }); + + it('shows loading state', () => { + render(); + expect(screen.getByTestId('file-explorer-loading')).toBeInTheDocument(); + }); + + it('shows empty state when no files', () => { + render(); + expect(screen.getByTestId('file-explorer-empty')).toBeInTheDocument(); + }); + + it('calls onFileClick when file row clicked', () => { + render(); + fireEvent.click(screen.getByTestId('file-row-f1')); + expect(defaultProps.onFileClick).toHaveBeenCalledWith(mockFiles[0]); + }); + + it('renders table view when viewMode is table', () => { + render(); + expect(screen.getByTestId('file-explorer-table')).toBeInTheDocument(); + }); + + it('renders icons view when viewMode is icons-md', () => { + render(); + expect(screen.getByTestId('file-explorer-icons-md')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/__tests__/dms/FileGrid.test.tsx b/frontend/src/__tests__/dms/FileGrid.test.tsx new file mode 100644 index 0000000..fc8970f --- /dev/null +++ b/frontend/src/__tests__/dms/FileGrid.test.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { FileGrid } from '@/components/dms/FileGrid'; +import type { DmsFile } from '@/api/dms'; + +const mockFiles: DmsFile[] = [ + { id: 'f1', folder_id: null, name: 'report.pdf', mime_type: 'application/pdf', size_bytes: 1024, storage_path: '/path/f1', deleted_at: null, uploaded_by: 'user1', created_at: '2024-01-01T00:00:00Z' }, + { id: 'f2', folder_id: null, name: 'photo.jpg', mime_type: 'image/jpeg', size_bytes: 4096, storage_path: '/path/f2', deleted_at: null, uploaded_by: 'user2', created_at: '2024-01-01T00:00:00Z' }, +]; + +const defaultProps = { + files: mockFiles, + selectedFileIds: new Set(), + onToggleSelect: vi.fn(), + onFileClick: vi.fn(), + onFileShare: vi.fn(), + onFileDelete: vi.fn(), + onFilePreview: vi.fn(), +}; + +describe('FileGrid', () => { + it('renders file grid container', () => { + render(); + expect(screen.getByTestId('file-grid')).toBeInTheDocument(); + }); + + it('renders file cards for each file', () => { + render(); + expect(screen.getByTestId('file-card-f1')).toBeInTheDocument(); + expect(screen.getByTestId('file-card-f2')).toBeInTheDocument(); + }); + + it('renders file names in cards', () => { + render(); + expect(screen.getByText('report.pdf')).toBeInTheDocument(); + expect(screen.getByText('photo.jpg')).toBeInTheDocument(); + }); + + it('shows loading state', () => { + render(); + expect(screen.getByTestId('file-grid-loading')).toBeInTheDocument(); + }); + + it('shows empty state when no files', () => { + render(); + expect(screen.getByTestId('file-grid-empty')).toBeInTheDocument(); + }); + + it('calls onFileClick when file card clicked', () => { + render(); + fireEvent.click(screen.getByTestId('file-card-f1')); + expect(defaultProps.onFileClick).toHaveBeenCalledWith(mockFiles[0]); + }); + + it('renders checkboxes for file selection', () => { + render(); + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes.length).toBeGreaterThanOrEqual(2); + }); +}); diff --git a/frontend/src/__tests__/dms/SourceTree.test.tsx b/frontend/src/__tests__/dms/SourceTree.test.tsx new file mode 100644 index 0000000..995dd32 --- /dev/null +++ b/frontend/src/__tests__/dms/SourceTree.test.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { SourceTree } from '@/components/dms/SourceTree'; +import type { DmsFolder, DmsFile } from '@/api/dms'; + +const mockFolders: DmsFolder[] = [ + { id: 'folder1', name: 'Documents', parent_id: null, created_by: 'user1', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', file_count: 5 }, + { id: 'folder2', name: 'Images', parent_id: null, created_by: 'user1', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', file_count: 3 }, +]; + +const mockSharedFiles: DmsFile[] = [ + { id: 'sf1', folder_id: null, name: 'shared.doc', mime_type: 'application/msword', size_bytes: 512, storage_path: '/path/sf1', deleted_at: null, uploaded_by: 'other', created_at: '2024-01-01T00:00:00Z' }, +]; + +const defaultProps = { + folders: mockFolders, + sharedFiles: mockSharedFiles, + selectedFolderId: null, + selectedSource: null as string | null, + onSelectFolder: vi.fn(), + onSelectSource: vi.fn(), + onDropFiles: vi.fn(), + onDropFolder: vi.fn(), +}; + +describe('SourceTree', () => { + it('renders source tree container', () => { + render(); + expect(screen.getByTestId('source-tree')).toBeInTheDocument(); + }); + + it('renders folder names', () => { + render(); + expect(screen.getByText('Documents')).toBeInTheDocument(); + expect(screen.getByText('Images')).toBeInTheDocument(); + }); + + it('shows loading state', () => { + render(); + expect(screen.getByTestId('source-tree-loading')).toBeInTheDocument(); + }); + + it('calls onSelectFolder when folder clicked', () => { + render(); + fireEvent.click(screen.getByText('Documents')); + expect(defaultProps.onSelectFolder).toHaveBeenCalled(); + }); + + it('calls onSelectSource when source clicked', () => { + render(); + // Click on "All Files" entry which calls onSelectSource('local') + const allFilesEntry = screen.getByText(/all files|alle dateien/i); + if (allFilesEntry) { + fireEvent.click(allFilesEntry); + expect(defaultProps.onSelectSource).toHaveBeenCalled(); + } + }); +});