Step 2 - LDF Loading: - ldfparser integration (Python) / custom regex parser (C++) - QTreeWidget with expandable signal rows, merged Value column - Hex/Dec toggle, FreeFormat schedule entries, auto-reload - Baud rate auto-detection from LDF Step 3 - Signal Editing: - Bit packing/unpacking (signal value ↔ frame bytes) - ReadOnlyColumnDelegate for per-column editability - Value clamping to signal width, recursion guard Step 4 - Rx Panel: - receive_rx_frame() API with timestamp, signal unpacking - Change highlighting (yellow), auto-scroll toggle, clear button - Dashboard view (in-place update per frame_id) Step 5 - Connection Panel: - ConnectionManager with state machine (Disconnected/Connecting/Connected/Error) - Port scanning (pyserial / QSerialPort), connect/disconnect with UI mapping Step 6 - BabyLIN Backend: - BabyLinBackend wrapping Lipowsky BabyLIN_library.py DLL - Mock mode for macOS/CI, device scan, SDF loading, signal access - Frame callbacks, raw command access Step 7 - Master Scheduler: - QTimer-based schedule execution with start/stop/pause - Frame sent callback with visual highlighting - Mock Rx simulation, manual send, global rate override Tests: Python 171 | C++ 124 (Steps 1-5 parity, Steps 6-7 Python-first) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
175 lines
5.9 KiB
C++
175 lines
5.9 KiB
C++
/**
|
|
* test_rx_realtime.cpp — Tests for Step 4: Rx panel real-time display.
|
|
*/
|
|
|
|
#include <QtTest/QtTest>
|
|
#include <QTreeWidget>
|
|
#include <QTreeWidgetItem>
|
|
#include <QCheckBox>
|
|
#include <QPushButton>
|
|
#include "main_window.h"
|
|
|
|
#ifndef LDF_SAMPLE_PATH
|
|
#error "LDF_SAMPLE_PATH must be defined by CMake"
|
|
#endif
|
|
|
|
class TestRxRealtime : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private:
|
|
MainWindow* m_window;
|
|
|
|
private slots:
|
|
void init()
|
|
{
|
|
m_window = new MainWindow();
|
|
m_window->loadLdfFile(QString(LDF_SAMPLE_PATH));
|
|
}
|
|
void cleanup() { delete m_window; m_window = nullptr; }
|
|
|
|
// ─── Frame Reception ──────────────────────────────────────────
|
|
|
|
void test_timestampUpdates()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x03, 0xC8});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
QString ts = item->text(0);
|
|
QVERIFY(ts != QString::fromUtf8("—"));
|
|
QVERIFY(ts.contains(":"));
|
|
}
|
|
|
|
void test_frameBytesStored()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x03, 0xC8});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
QVariantList bytes = item->data(4, Qt::UserRole).toList();
|
|
QCOMPARE(bytes[0].toInt(), 0x03);
|
|
QCOMPARE(bytes[1].toInt(), 0xC8);
|
|
}
|
|
|
|
void test_frameValueDisplayed()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x03, 0xC8});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
QVERIFY(item->text(4).contains("C8"));
|
|
}
|
|
|
|
void test_signalValuesUnpacked()
|
|
{
|
|
// Motor_Status: MotorStatus (bit 0, w2), MotorTemp (bit 8, w8)
|
|
m_window->receiveRxFrame(0x20, {0x03, 0xC8});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
QCOMPARE(item->child(0)->data(4, Qt::UserRole).toInt(), 3); // MotorStatus
|
|
QCOMPARE(item->child(1)->data(4, Qt::UserRole).toInt(), 200); // MotorTemp
|
|
}
|
|
|
|
void test_unknownFrameIdIgnored()
|
|
{
|
|
m_window->receiveRxFrame(0xFF, {0x00});
|
|
// No crash
|
|
}
|
|
|
|
void test_updateSameFrameTwice()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x10});
|
|
m_window->receiveRxFrame(0x20, {0x02, 0x20});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
QVariantList bytes = item->data(4, Qt::UserRole).toList();
|
|
QCOMPARE(bytes[0].toInt(), 0x02);
|
|
QCOMPARE(bytes[1].toInt(), 0x20);
|
|
}
|
|
|
|
// ─── Change Highlighting ──────────────────────────────────────
|
|
|
|
void test_changedSignalHighlighted()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x10});
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x20});
|
|
auto* tempSig = m_window->rxTable()->topLevelItem(0)->child(1);
|
|
QColor bg = tempSig->background(4).color();
|
|
QCOMPARE(bg.red(), 255);
|
|
QCOMPARE(bg.green(), 255);
|
|
}
|
|
|
|
void test_unchangedSignalNotHighlighted()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x10});
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x20});
|
|
auto* statusSig = m_window->rxTable()->topLevelItem(0)->child(0);
|
|
QCOMPARE(statusSig->background(4).style(), Qt::NoBrush);
|
|
}
|
|
|
|
void test_firstReceptionNoHighlight()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x10});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
for (int j = 0; j < item->childCount(); ++j)
|
|
QCOMPARE(item->child(j)->background(4).style(), Qt::NoBrush);
|
|
}
|
|
|
|
// ─── Auto-scroll ─────────────────────────────────────────────
|
|
|
|
void test_autoScrollDefaultOn()
|
|
{
|
|
QVERIFY(m_window->autoScrollCheck()->isChecked());
|
|
}
|
|
|
|
void test_autoScrollCanBeDisabled()
|
|
{
|
|
m_window->autoScrollCheck()->setChecked(false);
|
|
QVERIFY(!m_window->autoScrollCheck()->isChecked());
|
|
}
|
|
|
|
// ─── Clear ───────────────────────────────────────────────────
|
|
|
|
void test_clearResetsTimestamps()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x10});
|
|
m_window->onClearRx();
|
|
for (int i = 0; i < m_window->rxTable()->topLevelItemCount(); ++i)
|
|
QCOMPARE(m_window->rxTable()->topLevelItem(i)->text(0), QString::fromUtf8("—"));
|
|
}
|
|
|
|
void test_clearResetsValues()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x10});
|
|
m_window->onClearRx();
|
|
for (int i = 0; i < m_window->rxTable()->topLevelItemCount(); ++i)
|
|
QCOMPARE(m_window->rxTable()->topLevelItem(i)->text(4), QString::fromUtf8("—"));
|
|
}
|
|
|
|
void test_clearResetsHighlights()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x01, 0x10});
|
|
m_window->receiveRxFrame(0x20, {0x02, 0x20});
|
|
m_window->onClearRx();
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
for (int j = 0; j < item->childCount(); ++j)
|
|
QCOMPARE(item->child(j)->background(4).style(), Qt::NoBrush);
|
|
}
|
|
|
|
// ─── Hex/Dec ─────────────────────────────────────────────────
|
|
|
|
void test_rxHexMode()
|
|
{
|
|
m_window->receiveRxFrame(0x20, {0x03, 0xC8});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
QVERIFY(item->text(4).contains("C8"));
|
|
QVERIFY(item->child(1)->text(4).startsWith("0x"));
|
|
}
|
|
|
|
void test_rxDecMode()
|
|
{
|
|
m_window->hexModeCheck()->setChecked(false);
|
|
m_window->receiveRxFrame(0x20, {0x03, 0xC8});
|
|
auto* item = m_window->rxTable()->topLevelItem(0);
|
|
QVERIFY(item->text(4).contains("200"));
|
|
QVERIFY(!item->child(1)->text(4).startsWith("0x"));
|
|
m_window->hexModeCheck()->setChecked(true);
|
|
}
|
|
};
|
|
|
|
QTEST_MAIN(TestRxRealtime)
|
|
#include "test_rx_realtime.moc"
|