import React from 'react'; import {act, fireEvent, screen, waitFor} from '@testing-library/react'; import {FileBrowser} from './FileBrowser'; import {renderWithProviders} from '../test-utils'; import {SettingsContext} from '../contexts/contexts'; import {FilePreviewContext} from './FilePreview'; // Mock the API functions jest.mock('../api', () => ({ deleteFile: jest.fn(), ignoreDirectory: jest.fn(), makeDirectory: jest.fn(), movePaths: jest.fn(), renamePath: jest.fn(), unignoreDirectory: jest.fn(), })); // Mock the custom hooks const mockUseUploadFile = { setFiles: jest.fn(), progresses: {}, setDestination: jest.fn(), doClear: jest.fn(), tagsSelector: null, overwrite: false, setOverwrite: jest.fn(), overallProgress: 0, inProgress: false, }; jest.mock('../hooks/customHooks', () => ({ useBrowseFiles: jest.fn(), useMediaDirectory: jest.fn(() => '/media/wrolpi'), useWROLMode: jest.fn(() => false), useStatusFlag: jest.fn(() => false), useUploadFile: () => mockUseUploadFile, })); // Mock react-dropzone jest.mock('react-dropzone', () => ({ useDropzone: () => ({ getRootProps: () => ({}), getInputProps: () => ({}), }), })); // Import mocked hooks for manipulation import {useBrowseFiles, useWROLMode} from '../hooks/customHooks'; // Create wrapper with all required contexts const AllContextsWrapper = ({children}) => { const settingsValue = { settings: {ignored_directories: []}, fetchSettings: jest.fn(), }; const filePreviewValue = { setPreviewFile: jest.fn(), setCallbacks: jest.fn(), }; return ( {children} ); }; // Custom render with all contexts const renderFileBrowser = (ui, options = {}) => { return renderWithProviders( {ui}, options ); }; describe('FileBrowser', () => { // Note: Files are sorted alphabetically by path, so file.txt comes before testdir/ const mockBrowseFiles = [ {path: 'file.txt', size: 1024}, {path: 'testdir/', children: null, is_empty: true}, ]; beforeEach(() => { jest.clearAllMocks(); useBrowseFiles.mockReturnValue({ browseFiles: mockBrowseFiles, openFolders: [], setOpenFolders: jest.fn(), fetchFiles: jest.fn(), }); useWROLMode.mockReturnValue(false); }); describe('Directory Selection', () => { it('renders without crashing', async () => { renderFileBrowser(); // Should render the file browser table expect(screen.getByRole('table')).toBeInTheDocument(); }); it('handles selecting and unselecting a directory without error', async () => { renderFileBrowser(); // Find the directory - text includes trailing slash const directoryRow = screen.getByText('testdir/'); expect(directoryRow).toBeInTheDocument(); // Find the checkbox in the same row - file.txt is first (index 0), testdir/ is second (index 1) const checkboxes = screen.getAllByRole('checkbox'); const directoryCheckbox = checkboxes[1]; // Second checkbox is for the directory // Select the directory await act(async () => { fireEvent.click(directoryCheckbox); }); // Unselect the directory - this should NOT throw an error await act(async () => { fireEvent.click(directoryCheckbox); }); // If we get here without error, the test passes expect(screen.getByRole('table')).toBeInTheDocument(); }); it('upload button is enabled when nothing is selected', async () => { renderFileBrowser(); // Find the upload button (green button with upload icon) const buttons = screen.getAllByRole('button'); const uploadButton = buttons.find(btn => btn.classList.contains('green') || btn.querySelector('[class*="upload"]') ); // Button should exist and not be disabled expect(uploadButton).toBeInTheDocument(); expect(uploadButton).not.toBeDisabled(); }); it('upload button is enabled when single directory is selected', async () => { renderFileBrowser(); // Select the directory (second checkbox - testdir/) const checkboxes = screen.getAllByRole('checkbox'); const directoryCheckbox = checkboxes[1]; await act(async () => { fireEvent.click(directoryCheckbox); }); // Find the upload button const buttons = screen.getAllByRole('button'); const uploadButton = buttons.find(btn => btn.classList.contains('green')); expect(uploadButton).not.toBeDisabled(); }); it('upload button is disabled when file is selected', async () => { renderFileBrowser(); // Select the file (first checkbox - file.txt) const checkboxes = screen.getAllByRole('checkbox'); const fileCheckbox = checkboxes[0]; await act(async () => { fireEvent.click(fileCheckbox); }); // Find the upload button const buttons = screen.getAllByRole('button'); const uploadButton = buttons.find(btn => btn.classList.contains('green')); expect(uploadButton).toBeDisabled(); }); }); describe('Delete functionality', () => { it('removes deleted directory from openFolders', async () => { const mockSetOpenFolders = jest.fn(); const mockFetchFiles = jest.fn(); const mockDeleteFile = require('../api').deleteFile; mockDeleteFile.mockResolvedValue(); useBrowseFiles.mockReturnValue({ browseFiles: [ {path: 'testdir/', children: [{path: 'testdir/file.txt', size: 100}], is_empty: false}, ], openFolders: ['testdir/'], setOpenFolders: mockSetOpenFolders, fetchFiles: mockFetchFiles, }); renderFileBrowser(); // Select the directory (first checkbox - testdir/) const checkboxes = screen.getAllByRole('checkbox'); await act(async () => { fireEvent.click(checkboxes[0]); }); // Click delete button (red trash button) - uses APIButton which has confirm modal const deleteButton = screen.getAllByRole('button').find(btn => btn.classList.contains('red')); await act(async () => { fireEvent.click(deleteButton); }); // Confirm deletion in modal await waitFor(() => { expect(screen.getByText('Delete')).toBeInTheDocument(); }); const confirmButton = screen.getByText('Delete'); await act(async () => { fireEvent.click(confirmButton); }); // Verify setOpenFolders was called to remove the deleted directory await waitFor(() => { expect(mockSetOpenFolders).toHaveBeenCalledWith(null); }); }); it('does not modify openFolders when deleting files only', async () => { const mockSetOpenFolders = jest.fn(); const mockFetchFiles = jest.fn(); const mockDeleteFile = require('../api').deleteFile; mockDeleteFile.mockResolvedValue(); useBrowseFiles.mockReturnValue({ browseFiles: [ {path: 'file.txt', size: 1024}, {path: 'testdir/', children: null, is_empty: true}, ], openFolders: ['testdir/'], setOpenFolders: mockSetOpenFolders, fetchFiles: mockFetchFiles, }); renderFileBrowser(); // Select the file (first checkbox - file.txt) const checkboxes = screen.getAllByRole('checkbox'); await act(async () => { fireEvent.click(checkboxes[0]); }); // Click delete button const deleteButton = screen.getAllByRole('button').find(btn => btn.classList.contains('red')); await act(async () => { fireEvent.click(deleteButton); }); // Confirm deletion in modal await waitFor(() => { expect(screen.getByText('Delete')).toBeInTheDocument(); }); const confirmButton = screen.getByText('Delete'); await act(async () => { fireEvent.click(confirmButton); }); // Wait for delete to complete await waitFor(() => { expect(mockDeleteFile).toHaveBeenCalled(); }); // setOpenFolders should NOT have been called since we only deleted a file expect(mockSetOpenFolders).not.toHaveBeenCalled(); }); }); });