Tests text undo/redo with bidirectional AST sync, find/replaceAll, selection, and verifies AST stays consistent through edit operations. Defines TextEditor interface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
178 lines
6.0 KiB
C++
178 lines
6.0 KiB
C++
// Step 53 TDD Test: Classical editing operations
|
|
//
|
|
// Tests that classical text editing integrates with the AST:
|
|
// 1. Undo in text mode undoes both text and AST changes
|
|
// 2. Redo restores both text and AST
|
|
// 3. Find text in the text buffer
|
|
// 4. Replace text → AST updates accordingly
|
|
// 5. Copy/paste operations work in text mode
|
|
// 6. Text undo/redo integrates with orchestrator's operation journal
|
|
//
|
|
// Will fail until classical editing operations are implemented.
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <cassert>
|
|
#include "ast/ASTNode.h"
|
|
#include "ast/Module.h"
|
|
#include "ast/Function.h"
|
|
|
|
static bool contains(const std::string& haystack, const std::string& needle) {
|
|
return haystack.find(needle) != std::string::npos;
|
|
}
|
|
|
|
// Forward declaration — TextEditor class that wraps text ops + AST sync
|
|
class TextEditor {
|
|
public:
|
|
// Set initial content
|
|
void setContent(const std::string& text, const std::string& language);
|
|
|
|
// Get current text
|
|
std::string getContent() const;
|
|
|
|
// Get current AST
|
|
Module* getAST() const;
|
|
|
|
// Edit operations
|
|
void insertText(int position, const std::string& text);
|
|
void deleteText(int position, int length);
|
|
void replaceText(int position, int length, const std::string& replacement);
|
|
|
|
// Undo/redo (must also undo/redo AST changes)
|
|
void undo();
|
|
void redo();
|
|
bool canUndo() const;
|
|
bool canRedo() const;
|
|
|
|
// Find/replace
|
|
int find(const std::string& query, int startPos = 0) const;
|
|
int replaceAll(const std::string& query, const std::string& replacement);
|
|
|
|
// Clipboard
|
|
std::string getSelection(int start, int length) const;
|
|
};
|
|
|
|
int main() {
|
|
int passed = 0;
|
|
int failed = 0;
|
|
|
|
// --- Test 1: Text undo restores previous text ---
|
|
{
|
|
TextEditor editor;
|
|
editor.setContent("def hello():\n pass\n", "python");
|
|
|
|
std::string before = editor.getContent();
|
|
editor.replaceText(4, 5, "world"); // "hello" -> "world"
|
|
std::string after = editor.getContent();
|
|
|
|
assert(contains(after, "world") && "After replace, should contain 'world'");
|
|
assert(!contains(after, "hello") && "After replace, should not contain 'hello'");
|
|
|
|
editor.undo();
|
|
std::string undone = editor.getContent();
|
|
assert(contains(undone, "hello") && "After undo, should contain 'hello' again");
|
|
|
|
std::cout << "Test 1 PASS: Text undo restores previous content" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 2: Text undo also undoes AST changes ---
|
|
{
|
|
TextEditor editor;
|
|
editor.setContent("def hello():\n pass\n", "python");
|
|
|
|
Module* astBefore = editor.getAST();
|
|
auto fnsBefore = astBefore->getChildren("functions");
|
|
auto* fnBefore = static_cast<Function*>(fnsBefore[0]);
|
|
assert(fnBefore->name == "hello" && "Initial function name should be 'hello'");
|
|
|
|
// Rename function in text
|
|
editor.replaceText(4, 5, "world");
|
|
|
|
Module* astAfter = editor.getAST();
|
|
auto fnsAfter = astAfter->getChildren("functions");
|
|
auto* fnAfter = static_cast<Function*>(fnsAfter[0]);
|
|
assert(fnAfter->name == "world" && "After edit, function name should be 'world'");
|
|
|
|
// Undo
|
|
editor.undo();
|
|
Module* astUndone = editor.getAST();
|
|
auto fnsUndone = astUndone->getChildren("functions");
|
|
auto* fnUndone = static_cast<Function*>(fnsUndone[0]);
|
|
assert(fnUndone->name == "hello" && "After undo, AST function name should be 'hello'");
|
|
|
|
std::cout << "Test 2 PASS: Text undo also undoes AST changes" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 3: Redo restores both text and AST ---
|
|
{
|
|
TextEditor editor;
|
|
editor.setContent("def hello():\n pass\n", "python");
|
|
|
|
editor.replaceText(4, 5, "world");
|
|
editor.undo();
|
|
assert(!editor.canRedo() == false && "Should be able to redo after undo");
|
|
|
|
editor.redo();
|
|
std::string text = editor.getContent();
|
|
assert(contains(text, "world") && "After redo, text should contain 'world'");
|
|
|
|
Module* ast = editor.getAST();
|
|
auto fns = ast->getChildren("functions");
|
|
auto* fn = static_cast<Function*>(fns[0]);
|
|
assert(fn->name == "world" && "After redo, AST function name should be 'world'");
|
|
|
|
std::cout << "Test 3 PASS: Redo restores both text and AST" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 4: Find returns correct position ---
|
|
{
|
|
TextEditor editor;
|
|
editor.setContent("def hello():\n return 42\n", "python");
|
|
|
|
int pos = editor.find("return");
|
|
assert(pos >= 0 && "Should find 'return' in text");
|
|
assert(pos == 18 && "Position of 'return' should be at column after indent");
|
|
|
|
int notFound = editor.find("nonexistent");
|
|
assert(notFound == -1 && "Should return -1 for not found");
|
|
|
|
std::cout << "Test 4 PASS: Find locates text at correct position" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 5: ReplaceAll updates text and AST ---
|
|
{
|
|
TextEditor editor;
|
|
editor.setContent("def foo():\n return foo\n", "python");
|
|
|
|
int count = editor.replaceAll("foo", "bar");
|
|
assert(count == 2 && "Should replace 2 occurrences of 'foo'");
|
|
|
|
std::string text = editor.getContent();
|
|
assert(contains(text, "bar") && "Text should contain 'bar'");
|
|
assert(!contains(text, "foo") && "Text should not contain 'foo'");
|
|
|
|
std::cout << "Test 5 PASS: ReplaceAll updates text and count" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Test 6: getSelection returns correct substring ---
|
|
{
|
|
TextEditor editor;
|
|
editor.setContent("def hello():\n pass\n", "python");
|
|
|
|
std::string sel = editor.getSelection(4, 5);
|
|
assert(sel == "hello" && "Selection of 5 chars at pos 4 should be 'hello'");
|
|
|
|
std::cout << "Test 6 PASS: getSelection returns correct text" << std::endl;
|
|
++passed;
|
|
}
|
|
|
|
// --- Summary ---
|
|
std::cout << "\n=== Step 53 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
|
return failed > 0 ? 1 : 0;
|
|
}
|