feat: type selector overhaul, fuzzy search, address parser, value tracking

Redesign type selector popup with fuzzy subsequence matching, per-category
icons, field summary tooltips, compact chips, and pointer target primitives.
Add address expression parser with arithmetic and register support.
Enable track value changes by default.
This commit is contained in:
IChooseYou
2026-02-28 06:59:22 -07:00
committed by IChooseYou
parent 0d73575ea7
commit 6a51c904de
21 changed files with 2489 additions and 592 deletions

View File

@@ -668,6 +668,181 @@ private slots:
QVERIFY(newIdx >= 0);
QCOMPARE(m_doc->tree.nodes[newIdx].kind, NodeKind::UInt32);
}
// ── Helper node controller tests ──
void testAddHelper() {
uint64_t rootId = m_doc->tree.nodes[0].id;
int origSize = m_doc->tree.nodes.size();
// Simulate "Add Helper" — same code as context menu action
Node helper;
helper.id = m_doc->tree.m_nextId++;
helper.kind = NodeKind::Hex64;
helper.name = QStringLiteral("helper");
helper.parentId = rootId;
helper.offset = 0;
helper.isHelper = true;
helper.offsetExpr = QStringLiteral("base");
m_doc->undoStack.push(new RcxCommand(m_ctrl, cmd::Insert{helper, {}}));
QApplication::processEvents();
QCOMPARE(m_doc->tree.nodes.size(), origSize + 1);
const auto& h = m_doc->tree.nodes.back();
QCOMPARE(h.isHelper, true);
QCOMPARE(h.offsetExpr, QStringLiteral("base"));
QCOMPARE(h.name, QStringLiteral("helper"));
QCOMPARE(h.parentId, rootId);
}
void testAddHelperUndo() {
uint64_t rootId = m_doc->tree.nodes[0].id;
int origSize = m_doc->tree.nodes.size();
Node helper;
helper.id = m_doc->tree.m_nextId++;
helper.kind = NodeKind::Hex64;
helper.name = QStringLiteral("helper");
helper.parentId = rootId;
helper.offset = 0;
helper.isHelper = true;
helper.offsetExpr = QStringLiteral("base");
m_doc->undoStack.push(new RcxCommand(m_ctrl, cmd::Insert{helper, {}}));
QApplication::processEvents();
QCOMPARE(m_doc->tree.nodes.size(), origSize + 1);
// Undo: helper should be gone
m_doc->undoStack.undo();
QApplication::processEvents();
QCOMPARE(m_doc->tree.nodes.size(), origSize);
// Redo: helper should be back
m_doc->undoStack.redo();
QApplication::processEvents();
QCOMPARE(m_doc->tree.nodes.size(), origSize + 1);
QCOMPARE(m_doc->tree.nodes.back().isHelper, true);
}
void testChangeHelperExpression() {
uint64_t rootId = m_doc->tree.nodes[0].id;
// Add a helper
Node helper;
helper.id = m_doc->tree.m_nextId++;
helper.kind = NodeKind::Hex64;
helper.name = QStringLiteral("helper");
helper.parentId = rootId;
helper.offset = 0;
helper.isHelper = true;
helper.offsetExpr = QStringLiteral("base");
m_doc->undoStack.push(new RcxCommand(m_ctrl, cmd::Insert{helper, {}}));
QApplication::processEvents();
uint64_t helperId = m_doc->tree.nodes.back().id;
// Change expression
m_doc->undoStack.push(new RcxCommand(m_ctrl,
cmd::ChangeOffsetExpr{helperId, QStringLiteral("base"), QStringLiteral("base + 0x10")}));
QApplication::processEvents();
int idx = m_doc->tree.indexOfId(helperId);
QVERIFY(idx >= 0);
QCOMPARE(m_doc->tree.nodes[idx].offsetExpr, QStringLiteral("base + 0x10"));
// Undo: old expression restored
m_doc->undoStack.undo();
QApplication::processEvents();
idx = m_doc->tree.indexOfId(helperId);
QVERIFY(idx >= 0);
QCOMPARE(m_doc->tree.nodes[idx].offsetExpr, QStringLiteral("base"));
}
void testDeleteHelperPreservesStructSize() {
uint64_t rootId = m_doc->tree.nodes[0].id;
int spanBefore = m_doc->tree.structSpan(rootId);
// Add a helper
Node helper;
helper.id = m_doc->tree.m_nextId++;
helper.kind = NodeKind::Hex64;
helper.name = QStringLiteral("helper");
helper.parentId = rootId;
helper.offset = 0;
helper.isHelper = true;
helper.offsetExpr = QStringLiteral("base");
m_doc->undoStack.push(new RcxCommand(m_ctrl, cmd::Insert{helper, {}}));
QApplication::processEvents();
// Struct size unchanged after adding helper
QCOMPARE(m_doc->tree.structSpan(rootId), spanBefore);
// Remove helper
uint64_t helperId = m_doc->tree.nodes.back().id;
m_doc->undoStack.push(new RcxCommand(m_ctrl, cmd::Remove{helperId}));
QApplication::processEvents();
// Struct size still unchanged
QCOMPARE(m_doc->tree.structSpan(rootId), spanBefore);
}
void testHelperRenamePreservesExpression() {
uint64_t rootId = m_doc->tree.nodes[0].id;
// Add a helper
Node helper;
helper.id = m_doc->tree.m_nextId++;
helper.kind = NodeKind::Hex64;
helper.name = QStringLiteral("my_helper");
helper.parentId = rootId;
helper.offset = 0;
helper.isHelper = true;
helper.offsetExpr = QStringLiteral("base + field_u32");
m_doc->undoStack.push(new RcxCommand(m_ctrl, cmd::Insert{helper, {}}));
QApplication::processEvents();
uint64_t helperId = m_doc->tree.nodes.back().id;
// Rename the helper
m_doc->undoStack.push(new RcxCommand(m_ctrl,
cmd::Rename{helperId, QStringLiteral("my_helper"), QStringLiteral("renamed_helper")}));
QApplication::processEvents();
int idx = m_doc->tree.indexOfId(helperId);
QVERIFY(idx >= 0);
QCOMPARE(m_doc->tree.nodes[idx].name, QStringLiteral("renamed_helper"));
// Expression should be preserved
QCOMPARE(m_doc->tree.nodes[idx].offsetExpr, QStringLiteral("base + field_u32"));
QCOMPARE(m_doc->tree.nodes[idx].isHelper, true);
}
void testHelperTypeChangePreservesFlags() {
uint64_t rootId = m_doc->tree.nodes[0].id;
Node helper;
helper.id = m_doc->tree.m_nextId++;
helper.kind = NodeKind::Hex64;
helper.name = QStringLiteral("helper");
helper.parentId = rootId;
helper.offset = 0;
helper.isHelper = true;
helper.offsetExpr = QStringLiteral("base");
m_doc->undoStack.push(new RcxCommand(m_ctrl, cmd::Insert{helper, {}}));
QApplication::processEvents();
uint64_t helperId = m_doc->tree.nodes.back().id;
// Change kind to UInt32
m_doc->undoStack.push(new RcxCommand(m_ctrl,
cmd::ChangeKind{helperId, NodeKind::Hex64, NodeKind::UInt32}));
QApplication::processEvents();
int idx = m_doc->tree.indexOfId(helperId);
QVERIFY(idx >= 0);
QCOMPARE(m_doc->tree.nodes[idx].kind, NodeKind::UInt32);
// Helper flags must survive type change
QCOMPARE(m_doc->tree.nodes[idx].isHelper, true);
QCOMPARE(m_doc->tree.nodes[idx].offsetExpr, QStringLiteral("base"));
}
};
QTEST_MAIN(TestController)