@@ -194,3 +194,71 @@ def test_clean_options_reflect_checkbox_states(
194194 opts = call_args [0 ][1 ]
195195 assert opts .remove_tabs is True
196196 assert opts .clean_whitespace is False
197+
198+
199+ class TestFindHandler :
200+ def test_empty_term_is_no_op (self , window ):
201+ """Empty findLineEdit → early return at line 264."""
202+ window ._find_edit .setText ("" )
203+ window ._on_find_clicked () # should not raise or select anything
204+ assert not window ._plain_text_edit .textCursor ().hasSelection ()
205+
206+ def test_finds_existing_text (self , window , qtbot ):
207+ """find() succeeds — line 265 (found=True branch)."""
208+ window ._viewmodel .load_file ("/tmp/test.txt" )
209+ qtbot .wait (10 )
210+ window ._plain_text_edit .setPlainText ("hello world" )
211+ window ._find_edit .setText ("hello" )
212+ window ._on_find_clicked ()
213+ assert window ._plain_text_edit .textCursor ().hasSelection ()
214+
215+ def test_wraps_when_not_found_from_end (self , window , qtbot ):
216+ """find() returns False → cursor wraps to start — lines 267-271."""
217+ window ._plain_text_edit .setPlainText ("hello" )
218+ # Move cursor to end so the first find('hello') misses
219+ cursor = window ._plain_text_edit .textCursor ()
220+ cursor .movePosition (cursor .MoveOperation .End )
221+ window ._plain_text_edit .setTextCursor (cursor )
222+ window ._find_edit .setText ("hello" )
223+ window ._on_find_clicked () # wraps and finds from start
224+ assert window ._plain_text_edit .textCursor ().hasSelection ()
225+
226+
227+ class TestReplaceHandler :
228+ def test_empty_find_term_is_no_op (self , window ):
229+ """Empty findLineEdit → early return at line 278."""
230+ window ._find_edit .setText ("" )
231+ window ._on_replace_clicked () # should not raise
232+
233+ def test_replace_with_no_selection_calls_find (self , window , qtbot ):
234+ """No selection → skips replacement, falls through to find — lines 279-282."""
235+ window ._plain_text_edit .setPlainText ("hello world" )
236+ window ._find_edit .setText ("hello" )
237+ window ._replace_edit .setText ("goodbye" )
238+ window ._on_replace_clicked ()
239+ # find() advances cursor to 'hello' after replace falls through
240+ assert window ._plain_text_edit .textCursor ().hasSelection ()
241+
242+ def test_replace_matching_selection_inserts_replacement (self , window , qtbot ):
243+ """Matching selection → text replaced — line 281."""
244+ window ._plain_text_edit .setPlainText ("hello world" )
245+ window ._find_edit .setText ("hello" )
246+ window ._replace_edit .setText ("goodbye" )
247+ # Manually select 'hello'
248+ cursor = window ._plain_text_edit .textCursor ()
249+ cursor .setPosition (0 )
250+ cursor .setPosition (5 , cursor .MoveMode .KeepAnchor )
251+ window ._plain_text_edit .setTextCursor (cursor )
252+ window ._on_replace_clicked ()
253+ assert "goodbye" in window ._plain_text_edit .toPlainText ()
254+
255+
256+ class TestReplaceAllHandler :
257+ def test_replace_all_emits_content_updated (self , window , qtbot ):
258+ """_on_replace_all_clicked delegates to ViewModel — line 289."""
259+ window ._viewmodel .load_file ("/tmp/test.txt" )
260+ qtbot .wait (10 )
261+ window ._find_edit .setText ("hello" )
262+ window ._replace_edit .setText ("goodbye" )
263+ with qtbot .waitSignal (window ._viewmodel .content_updated , timeout = 1000 ):
264+ window ._on_replace_all_clicked ()
0 commit comments