""" Tests for the Drag-and-Drop Upload Zone component (Story 7.14). """ import pytest from pathlib import Path import tempfile from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import QMimeData, QUrl from src.ui.drop_zone import ( DropState, DroppedFiles, DropZoneWidget, FileDropArea, ) @pytest.fixture def app(qtbot): """Create QApplication for tests.""" return QApplication.instance() or QApplication([]) @pytest.fixture def temp_files(tmp_path): """Create temporary test files.""" files = [] for ext in [".txt", ".md", ".html", ".jpg"]: file_path = tmp_path / f"test{ext}" file_path.write_text(f"Test content {ext}") files.append(file_path) return files class TestDropState: """Tests for DropState enum.""" def test_state_values(self): """Test DropState enum values.""" assert DropState.IDLE.value == "idle" assert DropState.HOVER.value == "hover" assert DropState.PROCESSING.value == "processing" assert DropState.ERROR.value == "error" class TestDroppedFiles: """Tests for DroppedFiles.""" def test_creation(self): """Test creating DroppedFiles.""" files = [Path("file1.txt"), Path("file2.md")] folders = [Path("folder1")] invalid = ["file.exe"] dropped = DroppedFiles( files=files, folders=folders, invalid=invalid ) assert len(dropped.files) == 2 assert len(dropped.folders) == 1 assert len(dropped.invalid) == 1 class TestDropZoneWidget: """Tests for DropZoneWidget.""" def test_initialization(self, qtbot): """Test widget initialization.""" widget = DropZoneWidget() qtbot.addWidget(widget) assert widget._state == DropState.IDLE assert widget._drag_active is False def test_supported_extensions(self): """Test supported file extensions.""" expected = [".txt", ".md", ".html", ".htm"] assert DropZoneWidget.SUPPORTED_EXTENSIONS == expected def test_is_valid_file(self, qtbot): """Test file validation.""" widget = DropZoneWidget() qtbot.addWidget(widget) # Valid files assert widget._is_valid_file(Path("test.txt")) is True assert widget._is_valid_file(Path("test.md")) is True assert widget._is_valid_file(Path("test.html")) is True # Invalid files assert widget._is_valid_file(Path("test.jpg")) is False assert widget._is_valid_file(Path("test.exe")) is False assert widget._is_valid_file(Path("test")) is False def test_set_state_idle(self, qtbot): """Test setting IDLE state.""" widget = DropZoneWidget() qtbot.addWidget(widget) widget._set_state(DropState.IDLE) assert widget._state == DropState.IDLE def test_set_state_hover(self, qtbot): """Test setting HOVER state.""" widget = DropZoneWidget() qtbot.addWidget(widget) widget._set_state(DropState.HOVER) assert widget._state == DropState.HOVER def test_set_state_error(self, qtbot): """Test setting ERROR state.""" widget = DropZoneWidget() qtbot.addWidget(widget) widget._set_state(DropState.ERROR) assert widget._state == DropState.ERROR def test_set_state_processing(self, qtbot): """Test setting PROCESSING state.""" widget = DropZoneWidget() qtbot.addWidget(widget) widget._set_state(DropState.PROCESSING) assert widget._state == DropState.PROCESSING assert widget._progress_bar.isVisible() is True def test_get_valid_files_from_mime(self, qtbot, temp_files): """Test extracting valid files from mime data.""" widget = DropZoneWidget() qtbot.addWidget(widget) # Create mime data with URLs mime_data = QMimeData() urls = [QUrl.fromLocalFile(str(f)) for f in temp_files] mime_data.setUrls(urls) valid_urls = widget._get_valid_files(mime_data) # Should have 3 valid files (txt, md, html) assert len(valid_urls) == 3 def test_process_mime_data(self, qtbot, temp_files): """Test processing mime data.""" widget = DropZoneWidget() qtbot.addWidget(widget) # Create mime data with valid and invalid files mime_data = QMimeData() urls = [QUrl.fromLocalFile(str(f)) for f in temp_files] mime_data.setUrls(urls) dropped = widget._process_mime_data(mime_data) assert len(dropped.files) == 3 # txt, md, html assert len(dropped.invalid) == 1 # jpg assert len(dropped.folders) == 0 class TestFileDropArea: """Tests for FileDropArea.""" def test_initialization(self, qtbot): """Test area initialization.""" area = FileDropArea() qtbot.addWidget(area) assert area.file_count == 0 assert area.files == [] def test_on_files_dropped(self, qtbot, tmp_path): """Test handling dropped files.""" area = FileDropArea() qtbot.addWidget(area) # Create dropped files files = [tmp_path / "test1.txt", tmp_path / "test2.md"] for f in files: f.write_text("content") dropped = DroppedFiles(files=files, folders=[], invalid=[]) # Mock signal emission area._on_files_dropped(dropped) assert area.file_count == 2 assert len(area.files) == 2 def test_clear(self, qtbot, tmp_path): """Test clearing files.""" area = FileDropArea() qtbot.addWidget(area) # Add some files files = [tmp_path / "test1.txt", tmp_path / "test2.md"] for f in files: f.write_text("content") dropped = DroppedFiles(files=files, folders=[], invalid=[]) area._on_files_dropped(dropped) assert area.file_count == 2 # Clear area.clear() assert area.file_count == 0 assert area.files == [] def test_on_clear(self, qtbot, tmp_path): """Test clear button handler.""" area = FileDropArea() qtbot.addWidget(area) # Add files files = [tmp_path / "test.txt"] files[0].write_text("content") dropped = DroppedFiles(files=files, folders=[], invalid=[]) area._on_files_dropped(dropped) assert area.file_count == 1 # Click clear area._on_clear() assert area.file_count == 0 def test_update_display(self, qtbot, tmp_path): """Test display update.""" area = FileDropArea() qtbot.addWidget(area) # Add files for i in range(5): f = tmp_path / f"test{i}.txt" f.write_text(f"content {i}") area._dropped_files.append(f) area._update_display() assert area._file_list_widget.isVisible() is True assert "5 个文件" in area._count_label.text()