test(7.4): add tests for DMS sub-components

- FileExplorer: list/table/icons views, file rows, loading/empty states, click handler
- SourceTree: folder tree, loading state, folder/source selection
- FileGrid: file cards, checkboxes, loading/empty states, click handler
- FileDetails: metadata display, preview/share/delete buttons, close handler
- BulkActions: selection bar, move/delete/cancel buttons, file count

All 36 DMS sub-component tests passing.
This commit is contained in:
Agent Zero
2026-07-24 00:55:40 +02:00
parent 6c3ca5bef7
commit 4f2fa62ffa
5 changed files with 333 additions and 0 deletions
@@ -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(<BulkActions {...defaultProps} />);
expect(screen.getByTestId('bulk-actions-bar')).toBeInTheDocument();
});
it('does not render when no files selected', () => {
render(<BulkActions {...defaultProps} selectedFileIds={new Set()} />);
expect(screen.queryByTestId('bulk-actions-bar')).not.toBeInTheDocument();
});
it('renders selected file count', () => {
render(<BulkActions {...defaultProps} />);
expect(screen.getByText(/3/)).toBeInTheDocument();
});
it('renders move button', () => {
render(<BulkActions {...defaultProps} />);
expect(screen.getByText(/move|verschieben/i)).toBeInTheDocument();
});
it('renders delete button', () => {
render(<BulkActions {...defaultProps} />);
expect(screen.getByText(/delete|löschen/i)).toBeInTheDocument();
});
it('renders cancel/clear button', () => {
render(<BulkActions {...defaultProps} />);
expect(screen.getByText(/cancel|abbrechen/i)).toBeInTheDocument();
});
it('calls onClear when cancel button clicked', () => {
render(<BulkActions {...defaultProps} />);
fireEvent.click(screen.getByText(/cancel|abbrechen/i));
expect(defaultProps.onClear).toHaveBeenCalled();
});
});
@@ -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(<FileDetails {...defaultProps} />);
expect(screen.getByTestId('file-details')).toBeInTheDocument();
});
it('renders file name in header', () => {
render(<FileDetails {...defaultProps} />);
expect(screen.getAllByText('document.pdf').length).toBeGreaterThan(0);
});
it('renders empty state when no file selected', () => {
render(<FileDetails {...defaultProps} file={null} />);
expect(screen.getByTestId('file-details-empty')).toBeInTheDocument();
});
it('renders close button', () => {
render(<FileDetails {...defaultProps} />);
expect(screen.getByLabelText(/close|schließen/i)).toBeInTheDocument();
});
it('calls onClose when close button clicked', () => {
render(<FileDetails {...defaultProps} />);
fireEvent.click(screen.getByLabelText(/close|schließen/i));
expect(defaultProps.onClose).toHaveBeenCalled();
});
it('renders preview button', () => {
render(<FileDetails {...defaultProps} />);
const previewBtn = screen.getByText(/preview|vorschau/i);
expect(previewBtn).toBeInTheDocument();
});
it('calls onPreview when preview button clicked', () => {
render(<FileDetails {...defaultProps} />);
fireEvent.click(screen.getByText(/preview|vorschau/i));
expect(defaultProps.onPreview).toHaveBeenCalledWith(mockFile);
});
it('renders delete button', () => {
render(<FileDetails {...defaultProps} />);
expect(screen.getByText(/delete|löschen/i)).toBeInTheDocument();
});
it('calls onDelete when delete button clicked', () => {
render(<FileDetails {...defaultProps} />);
fireEvent.click(screen.getByText(/delete|löschen/i));
expect(defaultProps.onDelete).toHaveBeenCalledWith(mockFile);
});
});
@@ -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<string>(),
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(<FileExplorer {...defaultProps} />);
expect(screen.getByTestId('file-explorer-list')).toBeInTheDocument();
});
it('renders file rows for each file', () => {
render(<FileExplorer {...defaultProps} />);
expect(screen.getByTestId('file-row-f1')).toBeInTheDocument();
expect(screen.getByTestId('file-row-f2')).toBeInTheDocument();
});
it('renders file names', () => {
render(<FileExplorer {...defaultProps} />);
expect(screen.getByText('document.pdf')).toBeInTheDocument();
expect(screen.getByText('image.png')).toBeInTheDocument();
});
it('shows loading state', () => {
render(<FileExplorer {...defaultProps} loading={true} />);
expect(screen.getByTestId('file-explorer-loading')).toBeInTheDocument();
});
it('shows empty state when no files', () => {
render(<FileExplorer {...defaultProps} files={[]} />);
expect(screen.getByTestId('file-explorer-empty')).toBeInTheDocument();
});
it('calls onFileClick when file row clicked', () => {
render(<FileExplorer {...defaultProps} />);
fireEvent.click(screen.getByTestId('file-row-f1'));
expect(defaultProps.onFileClick).toHaveBeenCalledWith(mockFiles[0]);
});
it('renders table view when viewMode is table', () => {
render(<FileExplorer {...defaultProps} viewMode="table" />);
expect(screen.getByTestId('file-explorer-table')).toBeInTheDocument();
});
it('renders icons view when viewMode is icons-md', () => {
render(<FileExplorer {...defaultProps} viewMode="icons-md" />);
expect(screen.getByTestId('file-explorer-icons-md')).toBeInTheDocument();
});
});
@@ -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<string>(),
onToggleSelect: vi.fn(),
onFileClick: vi.fn(),
onFileShare: vi.fn(),
onFileDelete: vi.fn(),
onFilePreview: vi.fn(),
};
describe('FileGrid', () => {
it('renders file grid container', () => {
render(<FileGrid {...defaultProps} />);
expect(screen.getByTestId('file-grid')).toBeInTheDocument();
});
it('renders file cards for each file', () => {
render(<FileGrid {...defaultProps} />);
expect(screen.getByTestId('file-card-f1')).toBeInTheDocument();
expect(screen.getByTestId('file-card-f2')).toBeInTheDocument();
});
it('renders file names in cards', () => {
render(<FileGrid {...defaultProps} />);
expect(screen.getByText('report.pdf')).toBeInTheDocument();
expect(screen.getByText('photo.jpg')).toBeInTheDocument();
});
it('shows loading state', () => {
render(<FileGrid {...defaultProps} loading={true} />);
expect(screen.getByTestId('file-grid-loading')).toBeInTheDocument();
});
it('shows empty state when no files', () => {
render(<FileGrid {...defaultProps} files={[]} />);
expect(screen.getByTestId('file-grid-empty')).toBeInTheDocument();
});
it('calls onFileClick when file card clicked', () => {
render(<FileGrid {...defaultProps} />);
fireEvent.click(screen.getByTestId('file-card-f1'));
expect(defaultProps.onFileClick).toHaveBeenCalledWith(mockFiles[0]);
});
it('renders checkboxes for file selection', () => {
render(<FileGrid {...defaultProps} />);
const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes.length).toBeGreaterThanOrEqual(2);
});
});
@@ -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(<SourceTree {...defaultProps} />);
expect(screen.getByTestId('source-tree')).toBeInTheDocument();
});
it('renders folder names', () => {
render(<SourceTree {...defaultProps} />);
expect(screen.getByText('Documents')).toBeInTheDocument();
expect(screen.getByText('Images')).toBeInTheDocument();
});
it('shows loading state', () => {
render(<SourceTree {...defaultProps} loading={true} />);
expect(screen.getByTestId('source-tree-loading')).toBeInTheDocument();
});
it('calls onSelectFolder when folder clicked', () => {
render(<SourceTree {...defaultProps} />);
fireEvent.click(screen.getByText('Documents'));
expect(defaultProps.onSelectFolder).toHaveBeenCalled();
});
it('calls onSelectSource when source clicked', () => {
render(<SourceTree {...defaultProps} />);
// 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();
}
});
});