From 6d95085510186d35b0937e9cc2f5cb19ac80d359 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sun, 22 Feb 2026 19:03:37 +0200 Subject: [PATCH] Adding speaker_identification_widget tests --- .coveragerc | 7 + .../hugging_face_search_line_edit_test.py | 29 +-- .../speaker_identification_widget_test.py | 211 ++++++++++++++++++ 3 files changed, 234 insertions(+), 13 deletions(-) diff --git a/.coveragerc b/.coveragerc index 566ba584..c682e6aa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -8,5 +8,12 @@ omit = deepmultilingualpunctuation/* ctc_forced_aligner/* +[report] +exclude_also = + if sys.platform == "win32": + if platform.system\(\) == "Windows": + if platform.system\(\) == "Linux": + if platform.system\(\) == "Darwin": + [html] directory = coverage/html diff --git a/tests/widgets/hugging_face_search_line_edit_test.py b/tests/widgets/hugging_face_search_line_edit_test.py index f131ae2c..d9a5b312 100644 --- a/tests/widgets/hugging_face_search_line_edit_test.py +++ b/tests/widgets/hugging_face_search_line_edit_test.py @@ -18,6 +18,9 @@ def widget(qtbot: QtBot): mock_manager.finished.connect = MagicMock() w = HuggingFaceSearchLineEdit(network_access_manager=mock_manager) qtbot.add_widget(w) + # Prevent popup.show() from triggering a Wayland fatal protocol error + # in headless/CI environments where popup windows lack a transient parent. + w.popup.show = MagicMock() return w @@ -82,7 +85,7 @@ class TestHuggingFaceSearchLineEdit: mock_reply.readAll.return_value.data.return_value = json.dumps([]).encode() widget.on_request_response(mock_reply) assert widget.popup.count() == 0 - assert not widget.popup.isVisible() + widget.popup.show.assert_not_called() def test_on_request_response_item_has_user_role_data(self, widget): mock_reply = MagicMock(spec=QNetworkReply) @@ -111,10 +114,10 @@ class TestHuggingFaceSearchLineEdit: item.setData(Qt.ItemDataRole.UserRole, "openai/whisper-tiny") widget.popup.addItem(item) widget.popup.setCurrentItem(item) - widget.popup.show() - widget.on_select_item() - assert not widget.popup.isVisible() + with patch.object(widget.popup, 'hide') as mock_hide: + widget.on_select_item() + mock_hide.assert_called_once() def test_on_popup_selected_stops_timer(self, widget): widget.timer.start() @@ -128,19 +131,19 @@ class TestHuggingFaceSearchLineEdit: assert widget.eventFilter(other, event) is False def test_event_filter_mouse_press_hides_popup(self, widget): - widget.popup.show() event = MagicMock() event.type.return_value = QEvent.Type.MouseButtonPress - result = widget.eventFilter(widget.popup, event) + with patch.object(widget.popup, 'hide') as mock_hide: + result = widget.eventFilter(widget.popup, event) assert result is True - assert not widget.popup.isVisible() + mock_hide.assert_called_once() def test_event_filter_escape_hides_popup(self, widget, qtbot: QtBot): - widget.popup.show() event = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Escape, Qt.KeyboardModifier.NoModifier) - result = widget.eventFilter(widget.popup, event) + with patch.object(widget.popup, 'hide') as mock_hide: + result = widget.eventFilter(widget.popup, event) assert result is True - assert not widget.popup.isVisible() + mock_hide.assert_called_once() def test_event_filter_enter_selects_item(self, widget, qtbot: QtBot): item = QListWidgetItem("openai/whisper-tiny") @@ -168,7 +171,7 @@ class TestHuggingFaceSearchLineEdit: assert widget.eventFilter(widget.popup, event) is False def test_event_filter_other_key_hides_popup(self, widget): - widget.popup.show() event = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_A, Qt.KeyboardModifier.NoModifier) - widget.eventFilter(widget.popup, event) - assert not widget.popup.isVisible() + with patch.object(widget.popup, 'hide') as mock_hide: + widget.eventFilter(widget.popup, event) + mock_hide.assert_called_once() diff --git a/tests/widgets/speaker_identification_widget_test.py b/tests/widgets/speaker_identification_widget_test.py index 5b65514d..fc97a819 100644 --- a/tests/widgets/speaker_identification_widget_test.py +++ b/tests/widgets/speaker_identification_widget_test.py @@ -90,6 +90,217 @@ class TestSpeakerIdentificationWidget: assert (result == [[{'end_time': 8904, 'speaker': 'Speaker 0', 'start_time': 140, 'text': 'Bien venue dans. '}]] or result == [[{'end_time': 8904, 'speaker': 'Speaker 0', 'start_time': 140, 'text': 'Bienvenue dans. '}]]) + def test_identify_button_toggles_visibility(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + # Before: identify visible, cancel hidden + assert not widget.step_1_button.isHidden() + assert widget.cancel_button.isHidden() + + from PyQt6.QtCore import QThread as RealQThread + mock_thread = MagicMock(spec=RealQThread) + mock_thread.started = MagicMock() + mock_thread.started.connect = MagicMock() + + with patch.object(widget, '_cleanup_thread'), \ + patch('buzz.widgets.transcription_viewer.speaker_identification_widget.QThread', return_value=mock_thread), \ + patch.object(widget, 'worker', create=True): + # patch moveToThread on IdentificationWorker to avoid type error + with patch.object(IdentificationWorker, 'moveToThread'): + widget.on_identify_button_clicked() + + # After: identify hidden, cancel visible + assert widget.step_1_button.isHidden() + assert not widget.cancel_button.isHidden() + + widget.close() + + def test_cancel_button_resets_ui(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + # Simulate identification started + widget.step_1_button.setVisible(False) + widget.cancel_button.setVisible(True) + + with patch.object(widget, '_cleanup_thread'): + widget.on_cancel_button_clicked() + + assert not widget.step_1_button.isHidden() + assert widget.cancel_button.isHidden() + assert widget.progress_bar.value() == 0 + assert len(widget.progress_label.text()) > 0 + + widget.close() + + def test_on_progress_update_sets_label_and_bar(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + widget.on_progress_update("3/8 Loading alignment model") + + assert widget.progress_label.text() == "3/8 Loading alignment model" + assert widget.progress_bar.value() == 3 + + widget.close() + + def test_on_progress_update_step_8_enables_save(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + assert not widget.save_button.isEnabled() + + widget.on_progress_update("8/8 Identification done") + + assert widget.save_button.isEnabled() + assert widget.step_2_group_box.isEnabled() + assert widget.merge_speaker_sentences.isEnabled() + + widget.close() + + def test_on_identification_finished_empty_result(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + initial_row_count = widget.speaker_preview_row.count() + + widget.on_identification_finished([]) + + assert widget.identification_result == [] + # Empty result returns early — speaker preview row unchanged + assert widget.speaker_preview_row.count() == initial_row_count + + widget.close() + + def test_on_identification_finished_populates_speakers(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + result = [ + {'speaker': 'Speaker 0', 'start_time': 0, 'end_time': 3000, 'text': 'Hello world.'}, + {'speaker': 'Speaker 1', 'start_time': 3000, 'end_time': 6000, 'text': 'Hi there.'}, + ] + widget.on_identification_finished(result) + + assert widget.identification_result == result + # Two speaker rows should have been created + assert widget.speaker_preview_row.count() == 2 + + widget.close() + + def test_on_identification_error_resets_buttons(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + widget.step_1_button.setVisible(False) + widget.cancel_button.setVisible(True) + + widget.on_identification_error("Some error") + + assert not widget.step_1_button.isHidden() + assert widget.cancel_button.isHidden() + assert widget.progress_bar.value() == 0 + + widget.close() + + def test_on_save_no_merge(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + result = [ + {'speaker': 'Speaker 0', 'start_time': 0, 'end_time': 2000, 'text': 'Hello.'}, + {'speaker': 'Speaker 0', 'start_time': 2000, 'end_time': 4000, 'text': 'World.'}, + {'speaker': 'Speaker 1', 'start_time': 4000, 'end_time': 6000, 'text': 'Hi.'}, + ] + widget.on_identification_finished(result) + widget.merge_speaker_sentences.setChecked(False) + + with patch.object(widget.transcription_service, 'copy_transcription', return_value=uuid.uuid4()) as mock_copy, \ + patch.object(widget.transcription_service, 'update_transcription_as_completed') as mock_update: + widget.on_save_button_clicked() + + mock_copy.assert_called_once() + mock_update.assert_called_once() + segments = mock_update.call_args[0][1] + # No merge: 3 entries → 3 segments + assert len(segments) == 3 + + widget.close() + + def test_on_save_with_merge(self, qtbot: QtBot, transcription, transcription_service): + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + result = [ + {'speaker': 'Speaker 0', 'start_time': 0, 'end_time': 2000, 'text': 'Hello.'}, + {'speaker': 'Speaker 0', 'start_time': 2000, 'end_time': 4000, 'text': 'World.'}, + {'speaker': 'Speaker 1', 'start_time': 4000, 'end_time': 6000, 'text': 'Hi.'}, + ] + widget.on_identification_finished(result) + widget.merge_speaker_sentences.setChecked(True) + + with patch.object(widget.transcription_service, 'copy_transcription', return_value=uuid.uuid4()), \ + patch.object(widget.transcription_service, 'update_transcription_as_completed') as mock_update: + widget.on_save_button_clicked() + + segments = mock_update.call_args[0][1] + # Merge: two consecutive Speaker 0 entries → merged into 1; Speaker 1 → 1 = 2 total + assert len(segments) == 2 + assert "Speaker 0" in segments[0].text + assert "Hello." in segments[0].text + assert "World." in segments[0].text + + widget.close() + + def test_on_save_emits_transcriptions_updated(self, qtbot: QtBot, transcription, transcription_service): + updated_signal = MagicMock() + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + transcriptions_updated_signal=updated_signal, + ) + qtbot.addWidget(widget) + + result = [{'speaker': 'Speaker 0', 'start_time': 0, 'end_time': 1000, 'text': 'Hi.'}] + widget.on_identification_finished(result) + + new_id = uuid.uuid4() + with patch.object(widget.transcription_service, 'copy_transcription', return_value=new_id), \ + patch.object(widget.transcription_service, 'update_transcription_as_completed'): + widget.on_save_button_clicked() + + updated_signal.emit.assert_called_once_with(new_id) + + widget.close() + def test_batch_processing_with_many_words(self): """Test batch processing when there are more than 200 words.""" # Create mock punctuation model