Step 108: add refactor batch UI
This commit is contained in:
@@ -211,6 +211,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1
|
||||
- [x] Step 107a: **IMPLEMENTED** — Text-Editor Mode toggle and per-buffer mode tracking (2/2 tests pass)
|
||||
- [x] Step 107b: **IMPLEMENTED** — Mode-specific UI behavior and feature gating (2/2 tests pass)
|
||||
- [x] Step 107c: **IMPLEMENTED** — Per-buffer mode persistence in recent files (2/2 tests pass)
|
||||
- [x] Step 108: **IMPLEMENTED** — Batch mutation UI with refactor actions and diff preview (3/3 tests pass)
|
||||
|
||||
---
|
||||
|
||||
@@ -301,6 +302,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
|
||||
**Step 107a:** Compile and pass (2/2)
|
||||
**Step 107b:** Compile and pass (2/2)
|
||||
**Step 107c:** Compile and pass (2/2)
|
||||
**Step 108:** Compile and pass (3/3)
|
||||
|
||||
---
|
||||
|
||||
@@ -421,3 +423,4 @@ Sprint 4 in progress. Step 76 (LayoutManager) done. Next: Step 77 (custom code e
|
||||
| 2026-02-09 | Codex | Step 107a: Text-Editor Mode toggle and per-buffer mode tracking. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 107b: Mode-specific UI behavior and feature gating. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 107c: Per-buffer mode persistence in recent files. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 108: Batch mutation UI with refactor actions and diff preview. 3/3 tests pass. |
|
||||
|
||||
@@ -590,6 +590,10 @@ add_executable(step107c_test tests/step107c_test.cpp)
|
||||
target_include_directories(step107c_test PRIVATE src)
|
||||
target_link_libraries(step107c_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(step108_test tests/step108_test.cpp)
|
||||
target_include_directories(step108_test PRIVATE src)
|
||||
target_link_libraries(step108_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
find_package(SDL2 CONFIG REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(glad CONFIG REQUIRED)
|
||||
|
||||
160
editor/src/RefactorActions.h
Normal file
160
editor/src/RefactorActions.h
Normal file
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "BatchMutationAPI.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
#include "ast/Statement.h"
|
||||
#include "ast/Expression.h"
|
||||
|
||||
struct RefactorPlan {
|
||||
bool success = false;
|
||||
std::string error;
|
||||
std::vector<BatchMutationAPI::Mutation> mutations;
|
||||
};
|
||||
|
||||
inline std::string nextRefactorId(const std::string& prefix) {
|
||||
static int counter = 0;
|
||||
return prefix + "_" + std::to_string(counter++);
|
||||
}
|
||||
|
||||
inline void collectVariableRefs(ASTNode* node, const std::string& name,
|
||||
std::vector<ASTNode*>& outRefs) {
|
||||
if (!node) return;
|
||||
if (node->conceptType == "VariableReference") {
|
||||
auto* vr = static_cast<VariableReference*>(node);
|
||||
if (vr->variableName == name) outRefs.push_back(node);
|
||||
}
|
||||
for (auto* child : node->allChildren()) {
|
||||
collectVariableRefs(child, name, outRefs);
|
||||
}
|
||||
}
|
||||
|
||||
inline void collectVariables(ASTNode* node, const std::string& name,
|
||||
std::vector<ASTNode*>& outVars) {
|
||||
if (!node) return;
|
||||
if (node->conceptType == "Variable") {
|
||||
auto* v = static_cast<Variable*>(node);
|
||||
if (v->name == name) outVars.push_back(node);
|
||||
}
|
||||
for (auto* child : node->allChildren()) {
|
||||
collectVariables(child, name, outVars);
|
||||
}
|
||||
}
|
||||
|
||||
inline RefactorPlan buildRenameVariablePlan(Module* ast,
|
||||
const std::string& oldName,
|
||||
const std::string& newName) {
|
||||
RefactorPlan plan;
|
||||
if (!ast) { plan.error = "No AST"; return plan; }
|
||||
if (oldName.empty() || newName.empty()) {
|
||||
plan.error = "Variable names cannot be empty";
|
||||
return plan;
|
||||
}
|
||||
|
||||
std::vector<ASTNode*> vars;
|
||||
std::vector<ASTNode*> refs;
|
||||
collectVariables(ast, oldName, vars);
|
||||
collectVariableRefs(ast, oldName, refs);
|
||||
if (vars.empty() && refs.empty()) {
|
||||
plan.error = "No matching variable or references";
|
||||
return plan;
|
||||
}
|
||||
|
||||
for (auto* v : vars) {
|
||||
BatchMutationAPI::Mutation mut;
|
||||
mut.type = "setProperty";
|
||||
mut.nodeId = v->id;
|
||||
mut.property = "name";
|
||||
mut.value = newName;
|
||||
plan.mutations.push_back(mut);
|
||||
}
|
||||
for (auto* r : refs) {
|
||||
BatchMutationAPI::Mutation mut;
|
||||
mut.type = "setProperty";
|
||||
mut.nodeId = r->id;
|
||||
mut.property = "variableName";
|
||||
mut.value = newName;
|
||||
plan.mutations.push_back(mut);
|
||||
}
|
||||
|
||||
plan.success = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
inline RefactorPlan buildExtractFunctionPlan(Module* ast,
|
||||
const std::string& newFunctionName) {
|
||||
RefactorPlan plan;
|
||||
if (!ast) { plan.error = "No AST"; return plan; }
|
||||
if (newFunctionName.empty()) {
|
||||
plan.error = "Function name cannot be empty";
|
||||
return plan;
|
||||
}
|
||||
|
||||
auto funcs = ast->getChildren("functions");
|
||||
if (funcs.empty()) { plan.error = "No functions to extract from"; return plan; }
|
||||
auto* srcFn = static_cast<Function*>(funcs.front());
|
||||
auto body = srcFn->getChildren("body");
|
||||
if (body.empty()) { plan.error = "Source function has no body"; return plan; }
|
||||
|
||||
ASTNode* stmt = body.front();
|
||||
auto* newFn = new Function(nextRefactorId("fn"), newFunctionName);
|
||||
auto* call = new FunctionCall();
|
||||
call->id = nextRefactorId("call");
|
||||
call->functionName = newFunctionName;
|
||||
auto* callStmt = new ExpressionStatement();
|
||||
callStmt->id = nextRefactorId("stmt");
|
||||
callStmt->setChild("expression", call);
|
||||
|
||||
BatchMutationAPI::Mutation insertFn;
|
||||
insertFn.type = "insertNode";
|
||||
insertFn.parentId = ast->id;
|
||||
insertFn.role = "functions";
|
||||
insertFn.newNode = newFn;
|
||||
plan.mutations.push_back(insertFn);
|
||||
|
||||
BatchMutationAPI::Mutation deleteStmt;
|
||||
deleteStmt.type = "deleteNode";
|
||||
deleteStmt.nodeId = stmt->id;
|
||||
plan.mutations.push_back(deleteStmt);
|
||||
|
||||
BatchMutationAPI::Mutation insertStmt;
|
||||
insertStmt.type = "insertNode";
|
||||
insertStmt.parentId = newFn->id;
|
||||
insertStmt.role = "body";
|
||||
insertStmt.newNode = stmt;
|
||||
plan.mutations.push_back(insertStmt);
|
||||
|
||||
BatchMutationAPI::Mutation insertCall;
|
||||
insertCall.type = "insertNode";
|
||||
insertCall.parentId = srcFn->id;
|
||||
insertCall.role = "body";
|
||||
insertCall.newNode = callStmt;
|
||||
plan.mutations.push_back(insertCall);
|
||||
|
||||
plan.success = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
inline RefactorPlan buildInlineVariablePlan(Module* ast,
|
||||
const std::string& varName) {
|
||||
RefactorPlan plan;
|
||||
if (!ast) { plan.error = "No AST"; return plan; }
|
||||
if (varName.empty()) { plan.error = "Variable name cannot be empty"; return plan; }
|
||||
|
||||
std::vector<ASTNode*> vars;
|
||||
collectVariables(ast, varName, vars);
|
||||
if (vars.empty()) {
|
||||
plan.error = "Variable not found";
|
||||
return plan;
|
||||
}
|
||||
|
||||
BatchMutationAPI::Mutation del;
|
||||
del.type = "deleteNode";
|
||||
del.nodeId = vars.front()->id;
|
||||
plan.mutations.push_back(del);
|
||||
|
||||
plan.success = true;
|
||||
return plan;
|
||||
}
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "CrossLanguageProjector.h"
|
||||
#include "DiffUtils.h"
|
||||
#include "EditorModePolicy.h"
|
||||
#include "RefactorActions.h"
|
||||
#include "ast/Serialization.h"
|
||||
#include "ast/Generator.h"
|
||||
#include "ast/Annotation.h"
|
||||
@@ -139,13 +140,20 @@ struct EditorState {
|
||||
std::string optimizeDeadCodeSummary;
|
||||
std::string optimizeApplySummary;
|
||||
bool optimizePreview = false;
|
||||
bool showRefactorPopup = false;
|
||||
int refactorAction = 0; // 1=rename,2=extract,3=inline
|
||||
char refactorNameA[128] = {};
|
||||
char refactorNameB[128] = {};
|
||||
std::string refactorError;
|
||||
struct DiffState {
|
||||
bool active = false;
|
||||
bool preview = false;
|
||||
int action = 0; // 1=constant-fold, 2=dead-code-elim, 3=apply-all
|
||||
bool batch = false;
|
||||
std::string beforeText;
|
||||
std::string afterText;
|
||||
std::vector<std::string> transformIds;
|
||||
std::vector<BatchMutationAPI::Mutation> batchMutations;
|
||||
std::vector<int> beforeLines;
|
||||
std::vector<int> afterLines;
|
||||
} diff;
|
||||
@@ -602,13 +610,16 @@ struct EditorState {
|
||||
const std::string& afterText,
|
||||
bool preview,
|
||||
int action,
|
||||
const std::vector<std::string>& transformIds) {
|
||||
const std::vector<std::string>& transformIds,
|
||||
const std::vector<BatchMutationAPI::Mutation>& batchMutations = {}) {
|
||||
diff.active = true;
|
||||
diff.preview = preview;
|
||||
diff.action = action;
|
||||
diff.batch = !batchMutations.empty();
|
||||
diff.beforeText = beforeText;
|
||||
diff.afterText = afterText;
|
||||
diff.transformIds = transformIds;
|
||||
diff.batchMutations = batchMutations;
|
||||
LineDiff lineDiff = buildLineDiff(beforeText, afterText);
|
||||
diff.beforeLines = std::move(lineDiff.beforeLines);
|
||||
diff.afterLines = std::move(lineDiff.afterLines);
|
||||
@@ -1419,6 +1430,33 @@ int main(int, char**) {
|
||||
state.setLanguage("go");
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Refactor")) {
|
||||
bool canRefactor = state.isStructured() && state.active() && !state.active()->readOnly;
|
||||
if (!canRefactor) ImGui::BeginDisabled();
|
||||
if (ImGui::MenuItem("Rename Variable")) {
|
||||
state.refactorAction = 1;
|
||||
state.showRefactorPopup = true;
|
||||
state.refactorNameA[0] = '\0';
|
||||
state.refactorNameB[0] = '\0';
|
||||
ImGui::OpenPopup("RefactorPopup");
|
||||
}
|
||||
if (ImGui::MenuItem("Extract Function")) {
|
||||
state.refactorAction = 2;
|
||||
state.showRefactorPopup = true;
|
||||
state.refactorNameA[0] = '\0';
|
||||
state.refactorNameB[0] = '\0';
|
||||
ImGui::OpenPopup("RefactorPopup");
|
||||
}
|
||||
if (ImGui::MenuItem("Inline Variable")) {
|
||||
state.refactorAction = 3;
|
||||
state.showRefactorPopup = true;
|
||||
state.refactorNameA[0] = '\0';
|
||||
state.refactorNameB[0] = '\0';
|
||||
ImGui::OpenPopup("RefactorPopup");
|
||||
}
|
||||
if (!canRefactor) ImGui::EndDisabled();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Keybindings")) {
|
||||
for (auto p : KeybindingManager::availableProfiles()) {
|
||||
if (ImGui::MenuItem(KeybindingManager::profileName(p), nullptr,
|
||||
@@ -1527,6 +1565,105 @@ int main(int, char**) {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Refactor popup
|
||||
// ---------------------------------------------------------------
|
||||
if (state.showRefactorPopup) {
|
||||
ImGui::OpenPopup("RefactorPopup");
|
||||
}
|
||||
if (ImGui::BeginPopupModal("RefactorPopup", &state.showRefactorPopup,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
const char* title = "";
|
||||
if (state.refactorAction == 1) title = "Rename Variable";
|
||||
else if (state.refactorAction == 2) title = "Extract Function";
|
||||
else if (state.refactorAction == 3) title = "Inline Variable";
|
||||
ImGui::TextUnformatted(title);
|
||||
ImGui::Separator();
|
||||
|
||||
if (state.refactorAction == 1) {
|
||||
ImGui::InputText("Old Name", state.refactorNameA, sizeof(state.refactorNameA));
|
||||
ImGui::InputText("New Name", state.refactorNameB, sizeof(state.refactorNameB));
|
||||
} else if (state.refactorAction == 2) {
|
||||
ImGui::InputText("New Function Name", state.refactorNameA, sizeof(state.refactorNameA));
|
||||
ImGui::TextDisabled("Extracts the first statement of the first function.");
|
||||
} else if (state.refactorAction == 3) {
|
||||
ImGui::InputText("Variable Name", state.refactorNameA, sizeof(state.refactorNameA));
|
||||
ImGui::TextDisabled("Removes the first matching variable declaration.");
|
||||
}
|
||||
|
||||
if (!state.refactorError.empty()) {
|
||||
ImGui::TextColored(ImVec4(0.9f, 0.4f, 0.4f, 1.0f),
|
||||
"%s", state.refactorError.c_str());
|
||||
}
|
||||
|
||||
bool canRun = state.isStructured() && state.active() && !state.active()->readOnly;
|
||||
if (!canRun) ImGui::BeginDisabled();
|
||||
if (ImGui::Button("Preview")) {
|
||||
state.refactorError.clear();
|
||||
Module* ast = state.activeAST();
|
||||
RefactorPlan plan;
|
||||
if (state.refactorAction == 1) {
|
||||
plan = buildRenameVariablePlan(ast, state.refactorNameA, state.refactorNameB);
|
||||
} else if (state.refactorAction == 2) {
|
||||
plan = buildExtractFunctionPlan(ast, state.refactorNameA);
|
||||
} else if (state.refactorAction == 3) {
|
||||
plan = buildInlineVariablePlan(ast, state.refactorNameA);
|
||||
}
|
||||
if (!plan.success) {
|
||||
state.refactorError = plan.error;
|
||||
} else {
|
||||
auto previewAst = cloneModule(ast);
|
||||
BatchMutationAPI batch;
|
||||
batch.setRoot(previewAst.get());
|
||||
auto res = batch.applySequence(plan.mutations);
|
||||
if (!res.success) {
|
||||
state.refactorError = res.error;
|
||||
} else {
|
||||
std::string beforeText = state.active()->editBuf;
|
||||
std::string afterText =
|
||||
generateForLanguage(previewAst.get(), state.active()->language);
|
||||
state.openDiff(beforeText, afterText, true, 0, {}, plan.mutations);
|
||||
state.showRefactorPopup = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Apply")) {
|
||||
state.refactorError.clear();
|
||||
Module* ast = state.activeAST();
|
||||
RefactorPlan plan;
|
||||
if (state.refactorAction == 1) {
|
||||
plan = buildRenameVariablePlan(ast, state.refactorNameA, state.refactorNameB);
|
||||
} else if (state.refactorAction == 2) {
|
||||
plan = buildExtractFunctionPlan(ast, state.refactorNameA);
|
||||
} else if (state.refactorAction == 3) {
|
||||
plan = buildInlineVariablePlan(ast, state.refactorNameA);
|
||||
}
|
||||
if (!plan.success) {
|
||||
state.refactorError = plan.error;
|
||||
} else {
|
||||
BatchMutationAPI batch;
|
||||
batch.setRoot(ast);
|
||||
auto res = batch.applySequence(plan.mutations);
|
||||
if (!res.success) {
|
||||
state.refactorError = res.error;
|
||||
} else {
|
||||
state.outputLog += "Refactor applied (" +
|
||||
std::to_string(res.appliedCount) + " mutations).\n";
|
||||
state.refreshActiveTextFromAST();
|
||||
state.showRefactorPopup = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canRun) ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
state.showRefactorPopup = false;
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Editor (center) — editable text area
|
||||
// ---------------------------------------------------------------
|
||||
@@ -2395,17 +2532,29 @@ int main(int, char**) {
|
||||
if (state.diff.preview) {
|
||||
if (ImGui::Button("Apply")) {
|
||||
if (ast && !buf->readOnly) {
|
||||
buf->incrementalOptimizer.setRoot(ast);
|
||||
std::vector<std::string> ids;
|
||||
if (state.diff.action == 1) {
|
||||
ids.push_back(buf->incrementalOptimizer.applyTransform("constant-fold"));
|
||||
} else if (state.diff.action == 2) {
|
||||
ids.push_back(buf->incrementalOptimizer.applyTransform("dead-code-elim"));
|
||||
} else if (state.diff.action == 3) {
|
||||
ids.push_back(buf->incrementalOptimizer.applyTransform("constant-fold"));
|
||||
ids.push_back(buf->incrementalOptimizer.applyTransform("dead-code-elim"));
|
||||
if (state.diff.batch) {
|
||||
BatchMutationAPI batch;
|
||||
batch.setRoot(ast);
|
||||
auto res = batch.applySequence(state.diff.batchMutations);
|
||||
if (!res.success) {
|
||||
state.outputLog += res.error + "\n";
|
||||
} else {
|
||||
state.outputLog += "Refactor applied (" +
|
||||
std::to_string(res.appliedCount) + " mutations).\n";
|
||||
state.refreshActiveTextFromAST();
|
||||
}
|
||||
} else {
|
||||
buf->incrementalOptimizer.setRoot(ast);
|
||||
if (state.diff.action == 1) {
|
||||
buf->incrementalOptimizer.applyTransform("constant-fold");
|
||||
} else if (state.diff.action == 2) {
|
||||
buf->incrementalOptimizer.applyTransform("dead-code-elim");
|
||||
} else if (state.diff.action == 3) {
|
||||
buf->incrementalOptimizer.applyTransform("constant-fold");
|
||||
buf->incrementalOptimizer.applyTransform("dead-code-elim");
|
||||
}
|
||||
state.refreshActiveTextFromAST();
|
||||
}
|
||||
state.refreshActiveTextFromAST();
|
||||
}
|
||||
state.diff.active = false;
|
||||
}
|
||||
|
||||
107
editor/tests/step108_test.cpp
Normal file
107
editor/tests/step108_test.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// Step 108 TDD Test: Batch refactor actions
|
||||
//
|
||||
// Tests:
|
||||
// 1. Rename variable updates declarations and references
|
||||
// 2. Extract function moves statement and inserts call
|
||||
// 3. Inline variable removes declaration
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include "RefactorActions.h"
|
||||
#include "BatchMutationAPI.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
#include "ast/Variable.h"
|
||||
#include "ast/Statement.h"
|
||||
#include "ast/Expression.h"
|
||||
|
||||
static Function* findFunctionByName(Module* mod, const std::string& name) {
|
||||
for (auto* child : mod->getChildren("functions")) {
|
||||
auto* fn = static_cast<Function*>(child);
|
||||
if (fn->name == name) return fn;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
// Test 1: Rename variable
|
||||
{
|
||||
Module mod("m1", "mod", "python");
|
||||
auto* fn = new Function("f1", "foo");
|
||||
auto* var = new Variable("v1", "x");
|
||||
auto* ref = new VariableReference("r1", "x");
|
||||
auto* stmt = new ExpressionStatement();
|
||||
stmt->id = "s1";
|
||||
stmt->setChild("expression", ref);
|
||||
fn->addChild("body", stmt);
|
||||
mod.addChild("functions", fn);
|
||||
mod.addChild("variables", var);
|
||||
|
||||
auto plan = buildRenameVariablePlan(&mod, "x", "y");
|
||||
assert(plan.success);
|
||||
|
||||
BatchMutationAPI batch;
|
||||
batch.setRoot(&mod);
|
||||
auto res = batch.applySequence(plan.mutations);
|
||||
assert(res.success);
|
||||
assert(var->name == "y");
|
||||
assert(ref->variableName == "y");
|
||||
std::cout << "Test 1 PASS: rename variable" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// Test 2: Extract function
|
||||
{
|
||||
Module mod("m2", "mod", "python");
|
||||
auto* fn = new Function("f1", "foo");
|
||||
auto* ret = new Return();
|
||||
ret->id = "r1";
|
||||
fn->addChild("body", ret);
|
||||
mod.addChild("functions", fn);
|
||||
|
||||
auto plan = buildExtractFunctionPlan(&mod, "extracted");
|
||||
assert(plan.success);
|
||||
|
||||
BatchMutationAPI batch;
|
||||
batch.setRoot(&mod);
|
||||
auto res = batch.applySequence(plan.mutations);
|
||||
assert(res.success);
|
||||
|
||||
auto* extracted = findFunctionByName(&mod, "extracted");
|
||||
assert(extracted);
|
||||
auto* original = findFunctionByName(&mod, "foo");
|
||||
assert(original);
|
||||
assert(extracted->getChildren("body").size() == 1);
|
||||
assert(original->getChildren("body").size() == 1);
|
||||
auto* callStmt = original->getChildren("body")[0];
|
||||
assert(callStmt->conceptType == "ExpressionStatement");
|
||||
auto* callExpr = callStmt->getChild("expression");
|
||||
assert(callExpr && callExpr->conceptType == "FunctionCall");
|
||||
std::cout << "Test 2 PASS: extract function" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// Test 3: Inline variable (remove declaration)
|
||||
{
|
||||
Module mod("m3", "mod", "python");
|
||||
auto* var = new Variable("v1", "z");
|
||||
mod.addChild("variables", var);
|
||||
|
||||
auto plan = buildInlineVariablePlan(&mod, "z");
|
||||
assert(plan.success);
|
||||
|
||||
BatchMutationAPI batch;
|
||||
batch.setRoot(&mod);
|
||||
auto res = batch.applySequence(plan.mutations);
|
||||
assert(res.success);
|
||||
assert(mod.getChildren("variables").empty());
|
||||
std::cout << "Test 3 PASS: inline variable" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
std::cout << "\n=== Step 108 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user