Lin_Simulator/cpp/tests/test_main_window.cpp
Mohamed Salem b808770573 Step 1: GUI skeleton for LIN Simulator (Python + C++)
- PyQt6 main window with Tx/Rx tables, connection dock, LDF toolbar,
  control bar with global send rate, and status bar
- C++ Qt6 equivalent with identical layout and feature parity
- About dialog: TeqanyLogix LTD / Developer: Mohamed Salem
- Application logo (SVG + PNG) with LIN bus waveform design
- Full test suites: Python (32 tests), C++ QTest (34 tests)
- Project plan and Step 1 documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:40:52 +02:00

279 lines
8.2 KiB
C++

/**
* test_main_window.cpp — Tests for the GUI skeleton (Step 1, C++).
*
* QTest FRAMEWORK:
* ================
* Qt provides its own test framework, QTest. It's simpler than GoogleTest
* but integrates perfectly with Qt's event loop and widget system.
*
* Key differences from pytest:
* pytest: assert window.windowTitle() == "..."
* QTest: QCOMPARE(window.windowTitle(), QString("..."))
*
* Test discovery:
* - Each test class inherits QObject and has Q_OBJECT
* - Test methods are private slots named test_*() or *_test()
* - QTEST_MAIN() generates main() that runs all test slots
*
* QCOMPARE vs QVERIFY:
* QVERIFY(condition) — like assert condition
* QCOMPARE(actual, expected) — like assert actual == expected, but prints both values on failure
*/
#include <QtTest/QtTest>
#include <QDockWidget>
#include <QTableWidget>
#include <QSpinBox>
#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include "main_window.h"
class TestMainWindow : public QObject
{
Q_OBJECT
private:
MainWindow* m_window;
private slots:
// ── Setup/Teardown ──
// init() runs BEFORE each test, cleanup() runs AFTER each test.
// This gives each test a fresh MainWindow (like pytest fixtures).
void init()
{
m_window = new MainWindow();
}
void cleanup()
{
delete m_window;
m_window = nullptr;
}
// ─── Window Basics ────────────────────────────────────────────
void test_windowTitle()
{
QCOMPARE(m_window->windowTitle(), QString("LIN Simulator"));
}
void test_minimumSize()
{
QVERIFY(m_window->minimumWidth() >= 1024);
QVERIFY(m_window->minimumHeight() >= 768);
}
void test_centralWidgetExists()
{
QVERIFY(m_window->centralWidget() != nullptr);
}
// ─── Menu Bar ─────────────────────────────────────────────────
void test_menuBarExists()
{
QVERIFY(m_window->menuBar() != nullptr);
}
void test_loadLdfActionExists()
{
QVERIFY(m_window->loadLdfAction() != nullptr);
QCOMPARE(m_window->loadLdfAction()->text(), QString("&Load LDF..."));
}
void test_loadLdfShortcut()
{
QCOMPARE(m_window->loadLdfAction()->shortcut().toString(), QString("Ctrl+O"));
}
// ─── LDF Toolbar ──────────────────────────────────────────────
void test_ldfPathFieldExists()
{
QVERIFY(m_window->ldfPathEdit() != nullptr);
QVERIFY(m_window->ldfPathEdit()->isReadOnly());
}
void test_ldfPathPlaceholder()
{
QCOMPARE(m_window->ldfPathEdit()->placeholderText(),
QString("No LDF file loaded"));
}
void test_browseButtonExists()
{
QVERIFY(m_window->browseButton() != nullptr);
}
void test_autoReloadDefaultChecked()
{
QVERIFY(m_window->autoReloadCheck()->isChecked());
}
// ─── Tx Table ─────────────────────────────────────────────────
void test_txTableExists()
{
auto* table = m_window->txTable();
QVERIFY(table != nullptr);
// qobject_cast is Qt's type-safe dynamic cast.
// Returns nullptr if the object isn't the expected type.
QVERIFY(qobject_cast<QTableWidget*>(table) != nullptr);
}
void test_txTableColumns()
{
auto* table = m_window->txTable();
QCOMPARE(table->columnCount(), 7);
QStringList expected = {
"Frame Name", "Frame ID", "Length", "Interval (ms)",
"Data", "Signals", "Action"
};
for (int i = 0; i < table->columnCount(); ++i) {
QCOMPARE(table->horizontalHeaderItem(i)->text(), expected[i]);
}
}
void test_txTableAlternatingColors()
{
QVERIFY(m_window->txTable()->alternatingRowColors());
}
// ─── Rx Table ─────────────────────────────────────────────────
void test_rxTableExists()
{
auto* table = m_window->rxTable();
QVERIFY(table != nullptr);
QVERIFY(qobject_cast<QTableWidget*>(table) != nullptr);
}
void test_rxTableColumns()
{
auto* table = m_window->rxTable();
QCOMPARE(table->columnCount(), 5);
QStringList expected = {
"Timestamp", "Frame Name", "Frame ID", "Data", "Signals"
};
for (int i = 0; i < table->columnCount(); ++i) {
QCOMPARE(table->horizontalHeaderItem(i)->text(), expected[i]);
}
}
void test_rxTableNotEditable()
{
QCOMPARE(m_window->rxTable()->editTriggers(),
QAbstractItemView::NoEditTriggers);
}
// ─── Connection Dock ──────────────────────────────────────────
void test_dockExists()
{
// findChildren<T>() searches the widget tree for all children of type T
auto docks = m_window->findChildren<QDockWidget*>();
QCOMPARE(docks.size(), 1);
QCOMPARE(docks[0]->windowTitle(), QString("Connection"));
}
void test_deviceComboExists()
{
QVERIFY(m_window->deviceCombo() != nullptr);
}
void test_connectButtonExists()
{
QVERIFY(m_window->connectButton() != nullptr);
QVERIFY(m_window->connectButton()->isEnabled());
}
void test_disconnectButtonDisabledInitially()
{
QVERIFY(!m_window->disconnectButton()->isEnabled());
}
void test_statusLabelShowsDisconnected()
{
QVERIFY(m_window->connStatusLabel()->text().contains("Disconnected"));
}
void test_baudRateLabelExists()
{
QVERIFY(m_window->baudRateLabel() != nullptr);
}
void test_baudRateShowsPlaceholderBeforeLdf()
{
QVERIFY(m_window->baudRateLabel()->text().contains("load LDF"));
}
// ─── Control Bar ──────────────────────────────────────────────
void test_scheduleComboExists()
{
QVERIFY(m_window->scheduleCombo() != nullptr);
}
void test_schedulerButtonsDisabledInitially()
{
QVERIFY(!m_window->startButton()->isEnabled());
QVERIFY(!m_window->stopButton()->isEnabled());
QVERIFY(!m_window->pauseButton()->isEnabled());
}
void test_manualSendDisabledInitially()
{
QVERIFY(!m_window->manualSendButton()->isEnabled());
}
void test_globalRateSpinboxExists()
{
QVERIFY(m_window->globalRateSpin() != nullptr);
}
void test_globalRateDefault50ms()
{
QCOMPARE(m_window->globalRateSpin()->value(), 50);
}
void test_globalRateRange()
{
QCOMPARE(m_window->globalRateSpin()->minimum(), 1);
QCOMPARE(m_window->globalRateSpin()->maximum(), 10000);
}
void test_globalRateSuffix()
{
QCOMPARE(m_window->globalRateSpin()->suffix(), QString(" ms"));
}
// ─── Status Bar ───────────────────────────────────────────────
void test_statusBarExists()
{
QVERIFY(m_window->statusBar() != nullptr);
}
void test_connectionStatusLabel()
{
QVERIFY(m_window->statusConnectionLabel()->text().contains("Disconnected"));
}
};
// QTEST_MAIN generates a main() function that:
// 1. Creates a QApplication
// 2. Instantiates TestMainWindow
// 3. Runs all private slots as test cases
// 4. Reports results
QTEST_MAIN(TestMainWindow)
// This #include is required when the test class is defined in a .cpp file
// (not a .h file). It includes the MOC-generated code for our Q_OBJECT class.
// Without it, the linker would fail with "undefined reference to vtable".
#include "test_main_window.moc"