Phase 3c: Classical editing mode (Steps 50-53)
Implement TextASTSync and TextEditor — the core text editing layer that bridges raw text and the structured AST. TextASTSync provides bidirectional text-AST sync with debounce via TreeSitterParser and generators. TextEditor wraps a text buffer with edit operations, undo/redo that tracks AST state, find/replace, and selection. New files: TextASTSync.h, TextEditor.h, step50_test.cpp Updated: step51/53 tests (replaced stubs with real includes), CMakeLists.txt (tree-sitter linking), PROGRESS.md Fixed: step53 find-position off-by-one, canRedo assert logic 19/19 tests pass (step50: 8, step51: 5, step53: 6) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
139
editor/tests/step50_test.cpp
Normal file
139
editor/tests/step50_test.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
// Step 50 TDD Test: Text editor component
|
||||
//
|
||||
// Validates the TextEditor and TextASTSync components exist and
|
||||
// basic operations work: create, set content, get content, basic
|
||||
// insert/delete, language selection.
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include "TextASTSync.h"
|
||||
#include "TextEditor.h"
|
||||
|
||||
static bool contains(const std::string& haystack, const std::string& needle) {
|
||||
return haystack.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
// --- Test 1: TextASTSync default state ---
|
||||
{
|
||||
TextASTSync sync;
|
||||
assert(sync.getAST() == nullptr && "Default AST should be null");
|
||||
assert(!sync.isParsePending() && "No parse should be pending initially");
|
||||
|
||||
std::cout << "Test 1 PASS: TextASTSync default state" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 2: TextEditor set/get content ---
|
||||
{
|
||||
TextEditor editor;
|
||||
editor.setContent("hello world", "python");
|
||||
assert(editor.getContent() == "hello world" && "Content should match");
|
||||
|
||||
std::cout << "Test 2 PASS: TextEditor set/get content" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 3: TextEditor insert ---
|
||||
{
|
||||
TextEditor editor;
|
||||
editor.setContent("hello world", "python");
|
||||
editor.insertText(5, " beautiful");
|
||||
assert(editor.getContent() == "hello beautiful world" && "Insert should work");
|
||||
|
||||
std::cout << "Test 3 PASS: TextEditor insert" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 4: TextEditor delete ---
|
||||
{
|
||||
TextEditor editor;
|
||||
editor.setContent("hello world", "python");
|
||||
editor.deleteText(5, 6); // delete " world"
|
||||
assert(editor.getContent() == "hello" && "Delete should work");
|
||||
|
||||
std::cout << "Test 4 PASS: TextEditor delete" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 5: TextASTSync Python parse produces valid AST ---
|
||||
{
|
||||
TextASTSync sync;
|
||||
sync.setText("def test_func(x, y):\n return x + y\n", "python");
|
||||
sync.syncNow();
|
||||
|
||||
Module* ast = sync.getAST();
|
||||
assert(ast != nullptr && "AST should exist after sync");
|
||||
assert(ast->targetLanguage == "python" && "Language should be python");
|
||||
|
||||
auto functions = ast->getChildren("functions");
|
||||
assert(functions.size() == 1 && "Should have one function");
|
||||
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
assert(fn->name == "test_func" && "Function name should match");
|
||||
|
||||
auto params = fn->getChildren("parameters");
|
||||
assert(params.size() == 2 && "Should have two parameters");
|
||||
|
||||
std::cout << "Test 5 PASS: Python parse produces valid AST" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 6: TextEditor produces AST from content ---
|
||||
{
|
||||
TextEditor editor;
|
||||
editor.setContent("def my_func():\n pass\n", "python");
|
||||
|
||||
Module* ast = editor.getAST();
|
||||
assert(ast != nullptr && "TextEditor should produce AST");
|
||||
|
||||
auto functions = ast->getChildren("functions");
|
||||
assert(!functions.empty() && "Should have parsed function");
|
||||
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
assert(fn->name == "my_func" && "Function name should match");
|
||||
|
||||
std::cout << "Test 6 PASS: TextEditor produces AST from content" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 7: TextEditor undo/redo availability ---
|
||||
{
|
||||
TextEditor editor;
|
||||
editor.setContent("test", "python");
|
||||
assert(!editor.canUndo() && "Should not be able to undo initially");
|
||||
assert(!editor.canRedo() && "Should not be able to redo initially");
|
||||
|
||||
editor.insertText(4, "!");
|
||||
assert(editor.canUndo() && "Should be able to undo after edit");
|
||||
|
||||
editor.undo();
|
||||
assert(editor.canRedo() && "Should be able to redo after undo");
|
||||
assert(editor.getContent() == "test" && "Undo should restore text");
|
||||
|
||||
std::cout << "Test 7 PASS: Undo/redo availability tracking" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 8: TextASTSync C++ parse ---
|
||||
{
|
||||
TextASTSync sync;
|
||||
sync.setText("void hello() { return; }", "cpp");
|
||||
sync.syncNow();
|
||||
|
||||
Module* ast = sync.getAST();
|
||||
assert(ast != nullptr && "C++ AST should exist");
|
||||
assert(ast->targetLanguage == "cpp" && "Language should be cpp");
|
||||
|
||||
std::cout << "Test 8 PASS: C++ parse via TextASTSync" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Summary ---
|
||||
std::cout << "\n=== Step 50 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user