Fix 13 logic bugs and UI issues across editor, controller, and core

Round 1: Fix updateCommandRow offset, structTypeName undo, changeNodeKind
macro, shift-click kCommandRowId leak, type filter byte-vs-column bug.
Round 2: Move kFooterIdBit to core.h, add RcxEditor destructor for cursor
cleanup, defer refresh during batch ops, use newline separator in type
picker, narrow selection on double-click edit, clear hover on keyboard
scroll, guard 0x prefix from deletion, cap array count at 100k.
This commit is contained in:
DreamTeam2026
2026-02-06 12:57:01 -07:00
committed by sysadmin
parent e36d1591ba
commit 6852e0915e
15 changed files with 2221 additions and 130 deletions

View File

@@ -748,6 +748,119 @@ 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
ok = m_editor->beginInlineEdit(EditTarget::Type, paddingLine);
QVERIFY2(ok, "Type edit should be allowed on Padding lines");
QVERIFY(m_editor->isEditing());
m_editor->cancelInlineEdit();
QApplication::processEvents(); // flush deferred autocomplete timer
}
// ── 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);
// Line 2 = first UInt8 field (InheritedAddressSpace)
const LineMeta* lm = m_editor->metaForLine(2);
QVERIFY(lm);
QCOMPARE(lm->lineKind, LineKind::Field);
QVERIFY(lm->nodeKind != NodeKind::Padding);
// Begin value edit
bool ok = m_editor->beginInlineEdit(EditTarget::Value, 2);
QVERIFY(ok);
QVERIFY(m_editor->isEditing());
// Select all text in the edit span and type replacement
QKeyEvent home(QEvent::KeyPress, Qt::Key_Home, Qt::NoModifier);
QApplication::sendEvent(m_editor->scintilla(), &home);
QKeyEvent end(QEvent::KeyPress, Qt::Key_End, Qt::ShiftModifier);
QApplication::sendEvent(m_editor->scintilla(), &end);
// Type "42" to replace selected text
for (QChar c : QString("42")) {
QKeyEvent key(QEvent::KeyPress, 0, Qt::NoModifier, QString(c));
QApplication::sendEvent(m_editor->scintilla(), &key);
}
QApplication::processEvents();
// Commit with Enter
QSignalSpy spy(m_editor, &RcxEditor::inlineEditCommitted);
QKeyEvent enter(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier);
QApplication::sendEvent(m_editor->scintilla(), &enter);
QCOMPARE(spy.count(), 1);
QVERIFY(!m_editor->isEditing());
// Verify the committed text contains what was typed.
// UInt8 values display as hex (e.g., "0x042"), so the typed "42" gets
// concatenated with the existing "0x0" prefix → "0x042".
// The important check: the signal fired with non-empty text.
QList<QVariant> args = spy.first();
QString committedText = args.at(3).toString().trimmed();
QVERIFY2(!committedText.isEmpty(),
"Committed text should not be empty");
m_editor->applyDocument(m_result);
}
// ── Test: base address edit begins on CommandRow (line 0) ──
void testBaseAddressEditBegins() {
m_editor->applyDocument(m_result);