IChooseYou

This commit is contained in:
sysadmin
2026-02-15 09:23:17 -07:00
parent 4192a4dad3
commit 1473a58742
16 changed files with 191 additions and 416 deletions

View File

@@ -89,7 +89,7 @@ private slots:
QCOMPARE(result.meta[2].lineKind, LineKind::Footer);
}
void testPaddingMarker() {
void testHexNodeCompose() {
NodeTree tree;
tree.baseAddress = 0;
@@ -100,19 +100,18 @@ private slots:
int ri = tree.addNode(root);
uint64_t rootId = tree.nodes[ri].id;
Node pad;
pad.kind = NodeKind::Padding;
pad.name = "pad";
pad.parentId = rootId;
pad.offset = 0;
tree.addNode(pad);
Node hex;
hex.kind = NodeKind::Hex8;
hex.name = "pad";
hex.parentId = rootId;
hex.offset = 0;
tree.addNode(hex);
NullProvider prov;
ComposeResult result = compose(tree, prov);
// CommandRow + padding + root footer = 3
// CommandRow + hex node + root footer = 3
QCOMPARE(result.meta.size(), 3);
QVERIFY(result.meta[1].markerMask & (1u << M_PAD));
QCOMPARE(result.meta[1].depth, 1);
// Line 2 is root footer

View File

@@ -34,9 +34,8 @@ static void buildSmallTree(NodeTree& tree) {
field(0, NodeKind::UInt32, "field_u32"); // 4 bytes
field(4, NodeKind::Float, "field_float"); // 4 bytes
field(8, NodeKind::UInt8, "field_u8"); // 1 byte
field(9, NodeKind::Padding, "pad0"); // 3 bytes padding
// Set padding arrayLen = 3 for 3-byte padding
tree.nodes.last().arrayLen = 3;
field(9, NodeKind::Hex16, "pad0"); // 2 bytes
field(11, NodeKind::Hex8, "pad1"); // 1 byte
field(12, NodeKind::Hex32, "field_hex"); // 4 bytes
}
@@ -282,47 +281,6 @@ private slots:
QVERIFY(newIdx >= 0);
}
// ── Test: Padding value edit is effectively blocked at controller level ──
void testPaddingValueEditIsBlocked() {
// Find the padding node
int padIdx = -1;
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
if (m_doc->tree.nodes[i].kind == NodeKind::Padding) { padIdx = i; break; }
}
QVERIFY(padIdx >= 0);
uint64_t addr = m_doc->tree.computeOffset(padIdx);
// Read original data at padding offset
int padSize = m_doc->tree.nodes[padIdx].byteSize();
QByteArray origData = m_doc->provider->readBytes(addr, padSize);
// The context menu blocks Padding editing, so the controller's setNodeValue
// would only be called if the editing UI somehow allows it. But let's verify
// the editor correctly blocks it.
// Find padding line in composed output
ComposeResult result = m_doc->compose();
int paddingLine = -1;
for (int i = 0; i < result.meta.size(); i++) {
if (result.meta[i].nodeKind == NodeKind::Padding &&
result.meta[i].lineKind == LineKind::Field) {
paddingLine = i;
break;
}
}
QVERIFY(paddingLine >= 0);
m_editor->applyDocument(result);
QApplication::processEvents();
// beginInlineEdit(Value) on Padding line must be rejected
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Value, paddingLine));
QVERIFY(!m_editor->isEditing());
// Data must be unchanged
QByteArray afterData = m_doc->provider->readBytes(addr, padSize);
QCOMPARE(afterData, origData);
}
// ── Test: setNodeValue with Hex32 (space-separated hex bytes) ──
void testSetNodeValueHex() {
int idx = -1;

View File

@@ -170,9 +170,10 @@ static NodeTree makeTestTree() {
n.parentId = rootId; n.offset = off;
tree.addNode(n);
};
auto pad = [&](int off, int len, const char* name) {
Node n; n.kind = NodeKind::Padding; n.name = name;
n.parentId = rootId; n.offset = off; n.arrayLen = len;
auto pad = [&](int off, int /*len*/, const char* name) {
// 4-byte padding → Hex32 (all usages in this test pass len=4)
Node n; n.kind = NodeKind::Hex32; n.name = name;
n.parentId = rootId; n.offset = off;
tree.addNode(n);
};
auto arr = [&](int off, NodeKind ek, int len, const char* name) {
@@ -278,8 +279,8 @@ static NodeTree makeTestTree() {
n.kind = NodeKind::UInt16; n.name = "Length"; n.offset = 0; tree.addNode(n);
n.kind = NodeKind::UInt16; n.name = "MaximumLength"; n.offset = 2; tree.addNode(n);
n.kind = NodeKind::Padding; n.name = "Pad";
n.offset = 4; n.arrayLen = 4; tree.addNode(n);
n.kind = NodeKind::Hex32; n.name = "Pad";
n.offset = 4; n.arrayLen = 1; tree.addNode(n);
n.kind = NodeKind::Pointer64; n.name = "Buffer"; n.offset = 8; n.arrayLen = 1;
tree.addNode(n);
}
@@ -751,70 +752,6 @@ private slots:
m_editor->applyDocument(m_result);
}
// ── Test: Padding line rejects value editing ──
void testPaddingLineRejectsValueEdit() {
m_editor->applyDocument(m_result);
// Find a Padding line in the composed output
int paddingLine = -1;
for (int i = 0; i < m_result.meta.size(); i++) {
if (m_result.meta[i].nodeKind == NodeKind::Padding &&
m_result.meta[i].lineKind == LineKind::Field) {
paddingLine = i;
break;
}
}
QVERIFY2(paddingLine >= 0, "Should have at least one Padding line in test tree");
const LineMeta* lm = m_editor->metaForLine(paddingLine);
QVERIFY(lm);
QCOMPARE(lm->nodeKind, NodeKind::Padding);
// Value edit on Padding MUST be rejected (the bug fix)
QVERIFY2(!m_editor->beginInlineEdit(EditTarget::Value, paddingLine),
"Value edit should be rejected on Padding lines");
QVERIFY(!m_editor->isEditing());
// Name edit on Padding SHOULD succeed (ASCII preview column is editable)
bool ok = m_editor->beginInlineEdit(EditTarget::Name, paddingLine);
QVERIFY2(ok, "Name edit should be allowed on Padding lines (ASCII preview)");
QVERIFY(m_editor->isEditing());
m_editor->cancelInlineEdit();
// Type edit on Padding SHOULD succeed (emits popup signal)
QSignalSpy typeSpy(m_editor, &RcxEditor::typePickerRequested);
ok = m_editor->beginInlineEdit(EditTarget::Type, paddingLine);
QVERIFY2(ok, "Type edit should be allowed on Padding lines");
QCOMPARE(typeSpy.count(), 1);
}
// ── Test: resolvedSpanFor rejects Value on Padding (defense-in-depth) ──
void testPaddingLineRejectsValueSpan() {
m_editor->applyDocument(m_result);
// Find a Padding line
int paddingLine = -1;
for (int i = 0; i < m_result.meta.size(); i++) {
if (m_result.meta[i].nodeKind == NodeKind::Padding &&
m_result.meta[i].lineKind == LineKind::Field) {
paddingLine = i;
break;
}
}
QVERIFY(paddingLine >= 0);
const LineMeta* lm = m_editor->metaForLine(paddingLine);
QVERIFY(lm);
// valueSpanFor returns valid (shared with Hex via KF_HexPreview)
ColumnSpan vs = RcxEditor::valueSpan(*lm, 200);
QVERIFY2(vs.valid, "valueSpanFor should return valid for Padding (shared HexPreview flag)");
// But beginInlineEdit should still reject it
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Value, paddingLine));
QVERIFY(!m_editor->isEditing());
}
// ── Test: value edit commit fires signal with typed text ──
void testValueEditCommitUpdatesSignal() {
m_editor->applyDocument(m_result);
@@ -823,8 +760,6 @@ private slots:
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
QVERIFY(lm);
QCOMPARE(lm->lineKind, LineKind::Field);
QVERIFY(lm->nodeKind != NodeKind::Padding);
// Begin value edit
bool ok = m_editor->beginInlineEdit(EditTarget::Value, kFirstDataLine);
QVERIFY(ok);

View File

@@ -418,30 +418,6 @@ private slots:
QVERIFY(result.contains("wchar_t wname[32];"));
}
// ── Padding node ──
void testPaddingNode() {
rcx::NodeTree tree;
rcx::Node root;
root.kind = rcx::NodeKind::Struct;
root.name = "PadTest";
root.structTypeName = "PadTest";
root.parentId = 0;
int ri = tree.addNode(root);
uint64_t rootId = tree.nodes[ri].id;
rcx::Node pad;
pad.kind = rcx::NodeKind::Padding;
pad.name = "reserved";
pad.parentId = rootId;
pad.offset = 0;
pad.arrayLen = 16;
tree.addNode(pad);
QString result = rcx::renderCpp(tree, rootId);
QVERIFY(result.contains("uint8_t reserved[16];"));
}
// ── Full SDK export (multiple root structs) ──
void testFullSdkExport() {

View File

@@ -304,39 +304,6 @@ private slots:
QVERIFY(result.contains("float speed;"));
}
void testGenerator_typeAliases_padding() {
// Padding gap and tail padding should use aliased uint8_t
NodeTree tree;
Node root;
root.kind = NodeKind::Struct;
root.name = "PadTest";
root.structTypeName = "PadTest";
root.parentId = 0;
int ri = tree.addNode(root);
uint64_t rootId = tree.nodes[ri].id;
Node f1;
f1.kind = NodeKind::UInt32;
f1.name = "a";
f1.parentId = rootId;
f1.offset = 0;
tree.addNode(f1);
Node f2;
f2.kind = NodeKind::UInt32;
f2.name = "b";
f2.parentId = rootId;
f2.offset = 8; // gap of 4 bytes at offset 4
tree.addNode(f2);
QHash<NodeKind, QString> aliases;
aliases[NodeKind::Padding] = "BYTE";
QString result = renderCpp(tree, rootId, &aliases);
// Padding gap should use the alias
QVERIFY(result.contains("BYTE _pad"));
}
void testGenerator_typeAliases_array() {
// Array element type should use alias
NodeTree tree;
@@ -547,134 +514,92 @@ private slots:
void testWorkspace_simpleTree() {
auto tree = makeSimpleTree();
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "TestProject.rcx");
QVector<TabInfo> tabs = {{ &tree, "TestProject.rcx", nullptr }};
buildProjectExplorer(&model, tabs);
// 1 top-level item (the project)
// Single "Project" root
QCOMPARE(model.rowCount(), 1);
QStandardItem* project = model.item(0);
QCOMPARE(project->text(), QString("TestProject.rcx"));
QCOMPARE(project->text(), QString("Project"));
// Project has 1 child: the Player struct
// 1 type directly under Project: Player (no member fields)
QCOMPARE(project->rowCount(), 1);
QStandardItem* player = project->child(0);
QVERIFY(player->text().contains("Player"));
QVERIFY(player->text().contains("struct"));
// Player struct has 2 children: health, speed
QCOMPARE(player->rowCount(), 2);
QVERIFY(player->child(0)->text().contains("health"));
QVERIFY(player->child(1)->text().contains("speed"));
QVERIFY(project->child(0)->text().contains("Player"));
QVERIFY(project->child(0)->text().contains("struct"));
QCOMPARE(project->child(0)->rowCount(), 0);
}
void testWorkspace_twoRootTree() {
auto tree = makeTwoRootTree();
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "TwoRoot.rcx");
QVector<TabInfo> tabs = {{ &tree, "TwoRoot.rcx", nullptr }};
buildProjectExplorer(&model, tabs);
QCOMPARE(model.rowCount(), 1);
QStandardItem* project = model.item(0);
// 2 root struct children: Alpha and Bravo
// 2 types sorted alphabetically: Alpha, Bravo (no field children)
QCOMPARE(project->rowCount(), 2);
QVERIFY(project->child(0)->text().contains("Alpha"));
QVERIFY(project->child(1)->text().contains("Bravo"));
// Each has 1 field child
QCOMPARE(project->child(0)->rowCount(), 1);
QVERIFY(project->child(0)->child(0)->text().contains("flagsA"));
QCOMPARE(project->child(1)->rowCount(), 1);
QVERIFY(project->child(1)->child(0)->text().contains("flagsB"));
QCOMPARE(project->child(0)->rowCount(), 0);
QCOMPARE(project->child(1)->rowCount(), 0);
}
void testWorkspace_richTree_rootCount() {
auto tree = makeRichTree();
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "Rich.rcx");
QVector<TabInfo> tabs = {{ &tree, "Rich.rcx", nullptr }};
buildProjectExplorer(&model, tabs);
QStandardItem* project = model.item(0);
QCOMPARE(project->rowCount(), 3); // Pet, Cat, Ball
QCOMPARE(project->rowCount(), 3); // Ball, Cat, Pet (sorted)
}
void testWorkspace_richTree_petChildren() {
void testWorkspace_richTree_sorted() {
auto tree = makeRichTree();
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "Rich.rcx");
QVector<TabInfo> tabs = {{ &tree, "Rich.rcx", nullptr }};
buildProjectExplorer(&model, tabs);
QStandardItem* pet = model.item(0)->child(0);
QVERIFY(pet->text().contains("Pet"));
// Pet has 2 non-hex children: name (UTF8), owner (Pointer64)
QCOMPARE(pet->rowCount(), 2);
QVERIFY(pet->child(0)->text().contains("name"));
QVERIFY(pet->child(1)->text().contains("owner"));
}
void testWorkspace_richTree_catNesting() {
auto tree = makeRichTree();
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "Rich.rcx");
QStandardItem* cat = model.item(0)->child(1);
QVERIFY(cat->text().contains("Cat"));
// Find the nested "Pet" struct child (base)
QStandardItem* base = nullptr;
for (int i = 0; i < cat->rowCount(); i++) {
if (cat->child(i)->text().contains("Pet") &&
cat->child(i)->text().contains("struct")) {
base = cat->child(i);
break;
}
}
QVERIFY2(base != nullptr, "Cat should have a nested Pet struct child");
// base has structId set
QVERIFY(base->data(Qt::UserRole + 1).isValid());
// base should have its own children (name + owner)
QCOMPARE(base->rowCount(), 2);
}
void testWorkspace_richTree_ballChildren() {
auto tree = makeRichTree();
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "Rich.rcx");
QStandardItem* ball = model.item(0)->child(2);
QVERIFY(ball->text().contains("Ball"));
// Ball has 3 non-hex children: speed, position, color
QCOMPARE(ball->rowCount(), 3);
QVERIFY(ball->child(0)->text().contains("speed"));
QVERIFY(ball->child(1)->text().contains("position"));
QVERIFY(ball->child(2)->text().contains("color"));
QStandardItem* project = model.item(0);
// Sorted alphabetically: Ball, Cat, Pet
QVERIFY(project->child(0)->text().contains("Ball"));
QVERIFY(project->child(1)->text().contains("Cat"));
QVERIFY(project->child(2)->text().contains("Pet"));
// No member fields under type nodes
QCOMPARE(project->child(0)->rowCount(), 0);
QCOMPARE(project->child(1)->rowCount(), 0);
QCOMPARE(project->child(2)->rowCount(), 0);
}
void testWorkspace_emptyTree() {
NodeTree tree;
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "Empty.rcx");
QVector<TabInfo> tabs = {{ &tree, "Empty.rcx", nullptr }};
buildProjectExplorer(&model, tabs);
// Still has the "Project" root, just no children
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.item(0)->text(), QString("Project"));
QCOMPARE(model.item(0)->rowCount(), 0);
}
void testWorkspace_structIdRole() {
auto tree = makeSimpleTree();
QStandardItemModel model;
buildWorkspaceModel(&model, tree, "Test.rcx");
QVector<TabInfo> tabs = {{ &tree, "Test.rcx", nullptr }};
buildProjectExplorer(&model, tabs);
QStandardItem* project = model.item(0);
// Project item should NOT have structId
QVERIFY(!project->data(Qt::UserRole + 1).isValid());
// Project root has kGroupSentinel
QCOMPARE(project->data(Qt::UserRole + 1).toULongLong(), kGroupSentinel);
// Player struct should have structId
// Player type item should have structId
QStandardItem* player = project->child(0);
QVERIFY(player->data(Qt::UserRole + 1).isValid());
QVERIFY(player->data(Qt::UserRole + 1).toULongLong() > 0);
// health field should NOT have structId
QStandardItem* health = player->child(0);
QVERIFY(!health->data(Qt::UserRole + 1).isValid());
QVERIFY(player->data(Qt::UserRole + 1).toULongLong() != kGroupSentinel);
}
// ═══════════════════════════════════════════════════

View File

@@ -57,8 +57,8 @@ static void buildValidationTree(NodeTree& tree) {
field(46, NodeKind::Hex32, "field_h32");
field(50, NodeKind::Hex64, "field_h64");
field(58, NodeKind::Pointer64, "field_ptr");
field(66, NodeKind::Padding, "pad0");
tree.nodes.last().arrayLen = 6;
field(66, NodeKind::Hex32, "pad0");
field(70, NodeKind::Hex16, "pad1");
fieldArr(72, NodeKind::UInt32, 4, "field_arr");
}
@@ -725,9 +725,9 @@ private slots:
QCOMPARE(m_doc->undoStack.count(), 0);
}
// ── changeNodeKind size transitions: shrink inserts padding ──
// ── changeNodeKind size transitions: shrink inserts hex nodes ──
void testChangeKindShrinkInsertsPadding() {
void testChangeKindShrinkInsertsHexNodes() {
int idx = findNode(m_doc->tree, "field_u32");
QVERIFY(idx >= 0);
QCOMPARE(m_doc->tree.nodes[idx].kind, NodeKind::UInt32); // 4 bytes
@@ -737,7 +737,7 @@ private slots:
QApplication::processEvents();
QCOMPARE(m_doc->tree.nodes[idx].kind, NodeKind::UInt8);
// Should have inserted padding nodes (Hex16 + Hex8 = 3 bytes, or similar)
// Should have inserted hex nodes (Hex16 + Hex8 = 3 bytes, or similar)
QVERIFY(m_doc->tree.nodes.size() > origCount);
// Undo restores everything
@@ -985,37 +985,6 @@ private slots:
QVERIFY(!m_editor->isEditing());
}
// ── Editor: padding value edit blocked, name/type still work ──
void testPaddingEditRestrictions() {
m_ctrl->refresh();
QApplication::processEvents();
ComposeResult result = m_doc->compose();
m_editor->applyDocument(result);
QApplication::processEvents();
// Find padding line
int padLine = -1;
for (int i = 0; i < result.meta.size(); i++) {
if (result.meta[i].nodeKind == NodeKind::Padding &&
result.meta[i].lineKind == LineKind::Field) {
padLine = i;
break;
}
}
QVERIFY(padLine >= 0);
// Value edit rejected
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Value, padLine));
// Type edit accepted
bool ok = m_editor->beginInlineEdit(EditTarget::Type, padLine);
QVERIFY(ok);
m_editor->cancelInlineEdit();
QApplication::processEvents();
}
// ── Editor: struct header rejects value edit ──
void testStructHeaderRejectsValueEdit() {