test_drop_zone.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. """
  2. Tests for the Drag-and-Drop Upload Zone component (Story 7.14).
  3. """
  4. import pytest
  5. from pathlib import Path
  6. import tempfile
  7. from PyQt6.QtWidgets import QApplication
  8. from PyQt6.QtCore import QMimeData, QUrl
  9. from src.ui.drop_zone import (
  10. DropState,
  11. DroppedFiles,
  12. DropZoneWidget,
  13. FileDropArea,
  14. )
  15. @pytest.fixture
  16. def app(qtbot):
  17. """Create QApplication for tests."""
  18. return QApplication.instance() or QApplication([])
  19. @pytest.fixture
  20. def temp_files(tmp_path):
  21. """Create temporary test files."""
  22. files = []
  23. for ext in [".txt", ".md", ".html", ".jpg"]:
  24. file_path = tmp_path / f"test{ext}"
  25. file_path.write_text(f"Test content {ext}")
  26. files.append(file_path)
  27. return files
  28. class TestDropState:
  29. """Tests for DropState enum."""
  30. def test_state_values(self):
  31. """Test DropState enum values."""
  32. assert DropState.IDLE.value == "idle"
  33. assert DropState.HOVER.value == "hover"
  34. assert DropState.PROCESSING.value == "processing"
  35. assert DropState.ERROR.value == "error"
  36. class TestDroppedFiles:
  37. """Tests for DroppedFiles."""
  38. def test_creation(self):
  39. """Test creating DroppedFiles."""
  40. files = [Path("file1.txt"), Path("file2.md")]
  41. folders = [Path("folder1")]
  42. invalid = ["file.exe"]
  43. dropped = DroppedFiles(
  44. files=files,
  45. folders=folders,
  46. invalid=invalid
  47. )
  48. assert len(dropped.files) == 2
  49. assert len(dropped.folders) == 1
  50. assert len(dropped.invalid) == 1
  51. class TestDropZoneWidget:
  52. """Tests for DropZoneWidget."""
  53. def test_initialization(self, qtbot):
  54. """Test widget initialization."""
  55. widget = DropZoneWidget()
  56. qtbot.addWidget(widget)
  57. assert widget._state == DropState.IDLE
  58. assert widget._drag_active is False
  59. def test_supported_extensions(self):
  60. """Test supported file extensions."""
  61. expected = [".txt", ".md", ".html", ".htm"]
  62. assert DropZoneWidget.SUPPORTED_EXTENSIONS == expected
  63. def test_is_valid_file(self, qtbot):
  64. """Test file validation."""
  65. widget = DropZoneWidget()
  66. qtbot.addWidget(widget)
  67. # Valid files
  68. assert widget._is_valid_file(Path("test.txt")) is True
  69. assert widget._is_valid_file(Path("test.md")) is True
  70. assert widget._is_valid_file(Path("test.html")) is True
  71. # Invalid files
  72. assert widget._is_valid_file(Path("test.jpg")) is False
  73. assert widget._is_valid_file(Path("test.exe")) is False
  74. assert widget._is_valid_file(Path("test")) is False
  75. def test_set_state_idle(self, qtbot):
  76. """Test setting IDLE state."""
  77. widget = DropZoneWidget()
  78. qtbot.addWidget(widget)
  79. widget._set_state(DropState.IDLE)
  80. assert widget._state == DropState.IDLE
  81. def test_set_state_hover(self, qtbot):
  82. """Test setting HOVER state."""
  83. widget = DropZoneWidget()
  84. qtbot.addWidget(widget)
  85. widget._set_state(DropState.HOVER)
  86. assert widget._state == DropState.HOVER
  87. def test_set_state_error(self, qtbot):
  88. """Test setting ERROR state."""
  89. widget = DropZoneWidget()
  90. qtbot.addWidget(widget)
  91. widget._set_state(DropState.ERROR)
  92. assert widget._state == DropState.ERROR
  93. def test_set_state_processing(self, qtbot):
  94. """Test setting PROCESSING state."""
  95. widget = DropZoneWidget()
  96. qtbot.addWidget(widget)
  97. widget._set_state(DropState.PROCESSING)
  98. assert widget._state == DropState.PROCESSING
  99. assert widget._progress_bar.isVisible() is True
  100. def test_get_valid_files_from_mime(self, qtbot, temp_files):
  101. """Test extracting valid files from mime data."""
  102. widget = DropZoneWidget()
  103. qtbot.addWidget(widget)
  104. # Create mime data with URLs
  105. mime_data = QMimeData()
  106. urls = [QUrl.fromLocalFile(str(f)) for f in temp_files]
  107. mime_data.setUrls(urls)
  108. valid_urls = widget._get_valid_files(mime_data)
  109. # Should have 3 valid files (txt, md, html)
  110. assert len(valid_urls) == 3
  111. def test_process_mime_data(self, qtbot, temp_files):
  112. """Test processing mime data."""
  113. widget = DropZoneWidget()
  114. qtbot.addWidget(widget)
  115. # Create mime data with valid and invalid files
  116. mime_data = QMimeData()
  117. urls = [QUrl.fromLocalFile(str(f)) for f in temp_files]
  118. mime_data.setUrls(urls)
  119. dropped = widget._process_mime_data(mime_data)
  120. assert len(dropped.files) == 3 # txt, md, html
  121. assert len(dropped.invalid) == 1 # jpg
  122. assert len(dropped.folders) == 0
  123. class TestFileDropArea:
  124. """Tests for FileDropArea."""
  125. def test_initialization(self, qtbot):
  126. """Test area initialization."""
  127. area = FileDropArea()
  128. qtbot.addWidget(area)
  129. assert area.file_count == 0
  130. assert area.files == []
  131. def test_on_files_dropped(self, qtbot, tmp_path):
  132. """Test handling dropped files."""
  133. area = FileDropArea()
  134. qtbot.addWidget(area)
  135. # Create dropped files
  136. files = [tmp_path / "test1.txt", tmp_path / "test2.md"]
  137. for f in files:
  138. f.write_text("content")
  139. dropped = DroppedFiles(files=files, folders=[], invalid=[])
  140. # Mock signal emission
  141. area._on_files_dropped(dropped)
  142. assert area.file_count == 2
  143. assert len(area.files) == 2
  144. def test_clear(self, qtbot, tmp_path):
  145. """Test clearing files."""
  146. area = FileDropArea()
  147. qtbot.addWidget(area)
  148. # Add some files
  149. files = [tmp_path / "test1.txt", tmp_path / "test2.md"]
  150. for f in files:
  151. f.write_text("content")
  152. dropped = DroppedFiles(files=files, folders=[], invalid=[])
  153. area._on_files_dropped(dropped)
  154. assert area.file_count == 2
  155. # Clear
  156. area.clear()
  157. assert area.file_count == 0
  158. assert area.files == []
  159. def test_on_clear(self, qtbot, tmp_path):
  160. """Test clear button handler."""
  161. area = FileDropArea()
  162. qtbot.addWidget(area)
  163. # Add files
  164. files = [tmp_path / "test.txt"]
  165. files[0].write_text("content")
  166. dropped = DroppedFiles(files=files, folders=[], invalid=[])
  167. area._on_files_dropped(dropped)
  168. assert area.file_count == 1
  169. # Click clear
  170. area._on_clear()
  171. assert area.file_count == 0
  172. def test_update_display(self, qtbot, tmp_path):
  173. """Test display update."""
  174. area = FileDropArea()
  175. qtbot.addWidget(area)
  176. # Add files
  177. for i in range(5):
  178. f = tmp_path / f"test{i}.txt"
  179. f.write_text(f"content {i}")
  180. area._dropped_files.append(f)
  181. area._update_display()
  182. assert area._file_list_widget.isVisible() is True
  183. assert "5 个文件" in area._count_label.text()