#include "Test.h" #include "tests/TestHarness.h" using ktet::TestHarness; // These tests intentionally drive the prompt-based search/replace UI headlessly // via `Execute(Editor&, CommandId, ...)` to lock down behavior without ncurses. TEST (SearchFlow_FindStart_Success_LeavesCursorOnMatch_And_ClearsSearchState) { TestHarness h; Editor &ed = h.EditorRef(); Buffer &b = h.Buf(); b.insert_text(0, 0, "abc def abc"); b.SetCursor(0, 0); b.SetOffsets(0, 0); // Keep a mark set to ensure search doesn't clobber it. b.SetMark(0, 0); ASSERT_TRUE(b.MarkSet()); ASSERT_TRUE(h.Exec(CommandId::FindStart)); ASSERT_TRUE(ed.PromptActive()); ASSERT_EQ(ed.CurrentPromptKind(), Editor::PromptKind::Search); ASSERT_TRUE(ed.SearchActive()); // Typing into the prompt uses InsertText and should jump to the first match. ASSERT_TRUE(h.Exec(CommandId::InsertText, "def")); ASSERT_EQ(b.Cury(), (std::size_t) 0); ASSERT_EQ(b.Curx(), (std::size_t) 4); // Enter (Newline) accepts the prompt and ends incremental search. ASSERT_TRUE(h.Exec(CommandId::Newline)); ASSERT_TRUE(!ed.PromptActive()); ASSERT_TRUE(!ed.SearchActive()); ASSERT_TRUE(b.MarkSet()); } TEST (SearchFlow_FindStart_NotFound_RestoresOrigin_And_ClearsSearchState) { TestHarness h; Editor &ed = h.EditorRef(); Buffer &b = h.Buf(); b.insert_text(0, 0, "hello world\nsecond line\n"); b.SetCursor(3, 0); b.SetOffsets(1, 2); const std::size_t ox = b.Curx(); const std::size_t oy = b.Cury(); const std::size_t orow = b.Rowoffs(); const std::size_t ocol = b.Coloffs(); ASSERT_TRUE(h.Exec(CommandId::FindStart)); ASSERT_TRUE(ed.PromptActive()); ASSERT_TRUE(ed.SearchActive()); // Not-found should restore cursor/viewport to the saved origin while still in prompt. ASSERT_TRUE(h.Exec(CommandId::InsertText, "zzzz")); ASSERT_EQ(b.Curx(), ox); ASSERT_EQ(b.Cury(), oy); ASSERT_EQ(b.Rowoffs(), orow); ASSERT_EQ(b.Coloffs(), ocol); ASSERT_TRUE(h.Exec(CommandId::Newline)); ASSERT_TRUE(!ed.PromptActive()); ASSERT_TRUE(!ed.SearchActive()); } TEST (SearchFlow_SearchReplace_EmptyFind_DoesNotMutateBuffer_And_ClearsState) { TestHarness h; Editor &ed = h.EditorRef(); Buffer &b = h.Buf(); b.insert_text(0, 0, "abc abc\n"); b.SetCursor(0, 0); const std::string before = h.Text(); ASSERT_TRUE(h.Exec(CommandId::SearchReplace)); ASSERT_TRUE(ed.PromptActive()); ASSERT_EQ(ed.CurrentPromptKind(), Editor::PromptKind::ReplaceFind); // Accept empty find -> proceed to ReplaceWith. ASSERT_TRUE(h.Exec(CommandId::Newline)); ASSERT_TRUE(ed.PromptActive()); ASSERT_EQ(ed.CurrentPromptKind(), Editor::PromptKind::ReplaceWith); // Provide replacement and accept -> should cancel due to empty find. ASSERT_TRUE(h.Exec(CommandId::InsertText, "X")); ASSERT_TRUE(h.Exec(CommandId::Newline)); ASSERT_TRUE(!ed.PromptActive()); ASSERT_TRUE(!ed.SearchActive()); ASSERT_EQ(h.Text(), before); } TEST (SearchFlow_RegexFind_InvalidPattern_FailsSafely_And_ClearsStateOnEnter) { TestHarness h; Editor &ed = h.EditorRef(); Buffer &b = h.Buf(); b.insert_text(0, 0, "abc\ndef\n"); b.SetCursor(1, 0); b.SetOffsets(0, 0); const std::size_t ox = b.Curx(); const std::size_t oy = b.Cury(); ASSERT_TRUE(h.Exec(CommandId::RegexFindStart)); ASSERT_TRUE(ed.PromptActive()); ASSERT_EQ(ed.CurrentPromptKind(), Editor::PromptKind::RegexSearch); // Invalid regex should not crash; cursor should remain at origin due to no matches. ASSERT_TRUE(h.Exec(CommandId::InsertText, "(")); ASSERT_EQ(b.Curx(), ox); ASSERT_EQ(b.Cury(), oy); ASSERT_TRUE(h.Exec(CommandId::Newline)); ASSERT_TRUE(!ed.PromptActive()); ASSERT_TRUE(!ed.SearchActive()); }