From f230c4d4e3a5ba6a2c7f6404fa3301ebed33b1c1 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Thu, 1 Sep 2022 13:45:47 -0600 Subject: [PATCH 1/3] add test driver for GUI. --- gui/CMakeLists.txt | 70 +++++- gui/aboutdlg.h | 2 + gui/filterdlg.cc | 1 + gui/main.cc | 8 + gui/mainwindow.cc | 1 - gui/optionsdlg.cc | 11 + gui/test_main.cc | 549 +++++++++++++++++++++++++++++++++++++++++++++ gui/test_main.h | 52 +++++ 8 files changed, 685 insertions(+), 9 deletions(-) create mode 100644 gui/test_main.cc create mode 100644 gui/test_main.h diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 4c839c23a..94840e53a 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -19,13 +19,19 @@ if(UNIX AND NOT APPLE) else() set(TARGET GPSBabelFE) endif() +set(LIBTARGET ${TARGET}_lib) +set(TESTTARGET ${TARGET}_test) -add_executable(${TARGET} WIN32 MACOSX_BUNDLE) +add_library(${LIBTARGET} STATIC) +set(CMAKE_AUTOMOC OFF) +set(CMAKE_AUTOUIC OFF) +add_executable(${TARGET} WIN32 MACOSX_BUNDLE main.cc) +add_executable(${TESTTARGET} WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL test_main.cc test_main.h) # Find the QtCore library find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) -find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Network SerialPort Widgets Xml REQUIRED) -list(APPEND QT_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::SerialPort Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Xml) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Network SerialPort Widgets Xml Test REQUIRED) +list(APPEND QT_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::SerialPort Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Xml Qt${QT_VERSION_MAJOR}::Test) if(${Qt${QT_VERSION_MAJOR}Core_VERSION} VERSION_LESS 5.12) message(FATAL_ERROR "Qt version ${Qt${QT_VERSION_MAJOR}Core_VERSION} found, but version 5.12 or newer is required.") else() @@ -37,11 +43,19 @@ if (GPSBABEL_MAPPREVIEW) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS WebEngineWidgets WebChannel REQUIRED) list(APPEND QT_LIBRARIES Qt${QT_VERSION_MAJOR}::WebEngineWidgets Qt${QT_VERSION_MAJOR}::WebChannel) else() - target_compile_definitions(${TARGET} PRIVATE DISABLE_MAPPREVIEW) + target_compile_definitions(${LIBTARGET} PRIVATE DISABLE_MAPPREVIEW) endif() +if (${QT_VERSION_MAJOR} EQUAL "5") + qt5_wrap_cpp(TESTTARGET_MOC test_main.h TARGET ${TESTTARGET}) +else() + qt_wrap_cpp(TESTTARGET_MOC test_main.h TARGET ${TESTTARGET}) +endif() +target_sources(${TESTTARGET} PRIVATE ${TESTTARGET_MOC}) + if(UNIX AND NOT APPLE) set_target_properties(${TARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY GPSBabelFE) + set_target_properties(${TESTTARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY GPSBabelFE_test) endif() # RESOURCES @@ -83,7 +97,6 @@ if (GPSBABEL_MAPPREVIEW) endif() list(APPEND SOURCES help.cc) list(APPEND SOURCES latlng.cc) -list(APPEND SOURCES main.cc) list(APPEND SOURCES mainwindow.cc) if (GPSBABEL_MAPPREVIEW) list(APPEND SOURCES map.cc) @@ -138,28 +151,52 @@ else() endif() if (GPSBABEL_EMBED_TRANSLATIONS) list(APPEND RESOURCES translations.qrc) + target_compile_definitions(${TARGET} PRIVATE HAVE_EMBEDDED_TRANSLATIONS) + target_compile_definitions(${TESTTARGET} PRIVATE HAVE_EMBEDDED_TRANSLATIONS) endif() if (GPSBABEL_EMBED_MAP) list(APPEND RESOURCES map.qrc) + target_compile_definitions(${TARGET} PRIVATE HAVE_EMBEDDED_MAP) + target_compile_definitions(${TESTTARGET} PRIVATE HAVE_EMBEDDED_MAP) endif() +target_sources(${LIBTARGET} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES}) if(APPLE) set(MACOSX_BUNDLE_ICON_FILE appicon.icns) set(ICON_FILE images/${MACOSX_BUNDLE_ICON_FILE}) set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - target_sources(${TARGET} PRIVATE ${SOURCES} ${HEADERS} ${ICON_FILE} ${RESOURCES}) + target_sources(${TARGET} PRIVATE ${HEADERS} ${ICON_FILE}) + target_sources(${TESTTARGET} PRIVATE ${HEADERS} ${ICON_FILE}) # Info.plist has not been debugged with the cmake flow, it's a bit different than with the qmake flow. set_target_properties(${TARGET} PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER org.gpsbabel.${TARGET} MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} ) + set_target_properties(${TESTTARGET} PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER org.gpsbabel.${TESTTARGET} + MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} + ) +else() + target_sources(${TARGET} PRIVATE ${HEADERS}) + target_sources(${TESTTARGET} PRIVATE ${HEADERS}) +endif() + +get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(_isMultiConfig) + set(AUTOGEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/${LIBTARGET}_autogen/include_$) else() - target_sources(${TARGET} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES}) + set(AUTOGEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/${LIBTARGET}_autogen/include) endif() +target_include_directories(${LIBTARGET} INTERFACE $) -target_link_libraries(${TARGET} ${QT_LIBRARIES}) +target_link_libraries(${TARGET} ${LIBTARGET} ${QT_LIBRARIES}) +target_link_libraries(${TESTTARGET} ${LIBTARGET} ${QT_LIBRARIES}) +target_link_libraries(${LIBTARGET} ${QT_LIBRARIES}) + +get_target_property(idirs ${LIBTARGET} INTERFACE_INCLUDE_DIRECTORIES) +message(STATUS "lib incs \"${idirs}\"") get_target_property(Srcs ${TARGET} SOURCES) message(STATUS "Sources are: \"${Srcs}\"") @@ -171,3 +208,20 @@ get_target_property(IncDirs ${TARGET} INCLUDE_DIRECTORIES) message(STATUS "Include Directores are: \"${IncDirs}\"") add_custom_target(package_app COMMAND ./package_app DEPENDS ${TARGET}) + +add_custom_command( + TARGET ${TESTTARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "$" "$/gpsbabel" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/gmapbase.html" "$" + VERBATIM +) + +if(UNIX) + enable_testing() + add_test(NAME test-gui + COMMAND ${TESTTARGET} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +endif() + + diff --git a/gui/aboutdlg.h b/gui/aboutdlg.h index d20ca0af8..67d060bb2 100644 --- a/gui/aboutdlg.h +++ b/gui/aboutdlg.h @@ -30,6 +30,8 @@ class AboutDlg: public QDialog { + Q_OBJECT + public: AboutDlg(QWidget* parent, const QString& ver1, const QString& ver2, const QString& ver3, diff --git a/gui/filterdlg.cc b/gui/filterdlg.cc index d00f6e1fd..e986c7a4a 100644 --- a/gui/filterdlg.cc +++ b/gui/filterdlg.cc @@ -34,6 +34,7 @@ FilterDialog::FilterDialog(QWidget* parent, AllFiltersData& fd): QDialog(parent) ui_.filterList->clear(); widgetStack_ = new QStackedWidget(ui_.frame); + widgetStack_->setObjectName("widgetStack"); auto* layout = new QHBoxLayout(ui_.frame); layout->addWidget(widgetStack_); layout->setContentsMargins(2, 2, 2, 2); diff --git a/gui/main.cc b/gui/main.cc index 60788074e..e32e6bbe3 100644 --- a/gui/main.cc +++ b/gui/main.cc @@ -36,6 +36,14 @@ int main(int argc, char** argv) #error this version of Qt is not supported. #endif + Q_INIT_RESOURCE(app); +#ifdef HAVE_EMBEDDED_MAP + Q_INIT_RESOURCE(map); +#endif +#ifdef HAVE_EMBEDDED_TRANSLATIONS + Q_INIT_RESOURCE(translations); +#endif + QApplication app(argc, argv); QApplication::setWindowIcon(QIcon(":/images/appicon.png")); QApplication::setOrganizationName("GPSBabel"); diff --git a/gui/mainwindow.cc b/gui/mainwindow.cc index 4c4c27d73..13102990a 100644 --- a/gui/mainwindow.cc +++ b/gui/mainwindow.cc @@ -986,7 +986,6 @@ void MainWindow::applyActionX() if (babelData_.previewGmap_) { this->hide(); GMapDialog dlg(nullptr, tempName, babelData_.debugLevel_ >=1 ? ui_.outputWindow : nullptr); - dlg.show(); dlg.exec(); QFile(tempName).remove(); this->show(); diff --git a/gui/optionsdlg.cc b/gui/optionsdlg.cc index 59fabc516..76376f42c 100644 --- a/gui/optionsdlg.cc +++ b/gui/optionsdlg.cc @@ -91,9 +91,11 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListsetObjectName(objName); checkBox->setText(options_[k].getDescription()); horizontalLayout->addWidget(checkBox); checkBox->setChecked(options_[k].getSelected()); @@ -106,6 +108,7 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListsetObjectName(objName); SetSizeStuff(lineEdit); lineEdit->setText(getOptionValue(options_, k).toString()); w = lineEdit; @@ -117,7 +120,9 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListsetObjectName(objName); auto* button = new QToolButton(this); + button->setObjectName(objName); lineEdit->setText(getOptionValue(options_, k).toString()); button->setIcon(QIcon(inFile ? ":/images/open.png" : ":/images/save.png")); w = lineEdit; @@ -136,6 +141,7 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListsetObjectName(objName); SetSizeStuff(lineEdit); lineEdit->setText(getOptionValue(options_, k).toString()); w = lineEdit; @@ -152,6 +158,7 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListsetObjectName(objName); SetSizeStuff(lineEdit); w = lineEdit; int minVal = options_[k].getMinValue().toInt(); @@ -168,6 +175,7 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListsetObjectName(objName); spinBox->setRange(options_[k].getMinValue().toInt(), options_[k].getMaxValue().toInt()); spinBox->setValue(getOptionValue(options_, k).toInt()); @@ -185,6 +193,7 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListsetObjectName(objName); help->setIcon(QIcon(":/images/help.png")); help->setProperty("page", options[k].getHtml();) connect(help, SIGNAL(clicked()), this, SLOT(helpClicked())); @@ -194,6 +203,7 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListaddLayout(horizontalLayout); } auto* helpButton = new QPushButton(this); + helpButton->setObjectName("helpButton"); helpButton->setIcon(QIcon(":/images/help.png")); helpButton->setText(tr("Help")); @@ -201,6 +211,7 @@ OptionsDlg::OptionsDlg(QWidget* parent, const QString& fmtName, QListaddWidget(helpButton); buttonBox_ = new QDialogButtonBox(this); + buttonBox_->setObjectName("buttonBox"); buttonBox_->setOrientation(Qt::Horizontal); buttonBox_->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); lay->addWidget(buttonBox_); diff --git a/gui/test_main.cc b/gui/test_main.cc new file mode 100644 index 000000000..c9b2bcb9b --- /dev/null +++ b/gui/test_main.cc @@ -0,0 +1,549 @@ +#include "test_main.h" + +#include // for QAbstractButton +#include // for QApplication +#include // for QByteArray +#include // for QCheckBox +#include // for QComboBox +#include // for QDateTime +#include // for QDateTimeEdit +#include // for QDebug, operator<< +#include // for QDialog +#include // for QDialogButtonBox, QDialogButtonBox::Ok, QDialogButtonBox::Close +#include // for QLineEdit +#include // for QList, QList<>::const_iterator +#include // for QListWidget +#include // for QListWidgetItem +#include // for QMessageBox, QMessageBox::Yes +#include // for QPlainTextEdit +#include // for QPushButton +#include // for QRadioButton +#include // for QRect +#include // for QRegularExpression +#include // for QSettings +#include // for QStackedWidget +#include // for QTimer +#include // for LeftButton, Checked, Unchecked, ControlModifier, ISODate, Key_Home, Key_K, MatchExactly, NoModifier +#include // for qDebug, qPrintable +#include // for QVERIFY, QFAIL, QVERIFY2, QCOMPARE, QTEST_MAIN + +#include // for move + +#include "mainwindow.h" // for MainWindow + +test1::test1() +{ + qputenv("QTEST_FUNCTION_TIMEOUT", "120000"); + + Q_INIT_RESOURCE(app); +#ifdef HAVE_EMBEDDED_MAP + Q_INIT_RESOURCE(map); +#endif +#ifdef HAVE_EMBEDDED_TRANSLATIONS + Q_INIT_RESOURCE(translations); +#endif + + // isolate Qt Settings storage from real application. + QApplication::setOrganizationName("GPSBabel"); + QApplication::setOrganizationDomain("gpsbabel.org"); + QApplication::setApplicationName("GPSBabel-TEST"); +} + +test1::~test1() +{ + +} + +void test1::initTestCase() +{ +#if 1 + QSettings settings; + settings.clear(); +#endif +} + +void test1::cleanupTestCase() +{ +#if 0 + QSettings settings; + settings.clear(); +#endif +} + +void test1::dialogcb() +{ + (this->*dialogHandler_)(status_); +} + +void test1::runDialog(dialogStatus* status, dialog_cb dialogHandler, QWidget* button) +{ + (*status).errorCode = 0; + status_ = status; + dialogHandler_ = dialogHandler; + QTimer::singleShot(100, this, &test1::dialogcb); + if (button != nullptr) { + QTest::mouseClick(button, Qt::LeftButton); + } +} + +void test1::test_case1() +{ + dialogStatus status; + + MainWindow mainWindow(nullptr); + mainWindow.show(); + + //mainWindow.dumpObjectTree(); + + auto* actionAbout = mainWindow.findChild("actionAbout"); + QVERIFY(actionAbout != nullptr); + runDialog(&status, &test1::AboutDialogTester, nullptr); + // FIXME: not keyboard/mouse simulation + actionAbout->trigger(); + QVERIFY2(status.errorCode == 0, qPrintable(status.message)); + + auto* actionPreferences = mainWindow.findChild("actionPreferences"); + QVERIFY(actionPreferences != nullptr); + runDialog(&status, &test1::PreferencesDialogTester, nullptr); + // FIXME: not keyboard/mouse simulation + actionPreferences->trigger(); + QVERIFY2(status.errorCode == 0, qPrintable(status.message)); + + auto* inputFileOptBtn = mainWindow.findChild("inputFileOptBtn"); + QVERIFY(inputFileOptBtn != nullptr); + inputFileOptBtn->click(); + QVERIFY(inputFileOptBtn->isChecked()); + + auto* inputFormatCombo = mainWindow.findChild("inputFormatCombo"); + QVERIFY(inputFormatCombo != nullptr); + int iidx = inputFormatCombo->findText("GPX XML"); + QVERIFY(iidx >= 0); + // FIXME: not keyboard/mouse simulation + inputFormatCombo->setCurrentIndex(iidx); + + auto* inputFileNameText = mainWindow.findChild("inputFileNameText"); + QVERIFY(inputFileNameText != nullptr); + inputFileNameText->insert("reference/bounds-test.gpx"); + + auto* xlateWayPtsCk = mainWindow.findChild("xlateWayPtsCk"); + QVERIFY(xlateWayPtsCk != nullptr); + xlateWayPtsCk->setCheckState(Qt::Checked); + + auto* xlateRoutesCk = mainWindow.findChild("xlateRoutesCk"); + QVERIFY(xlateRoutesCk != nullptr); + xlateRoutesCk->setCheckState(Qt::Unchecked); + + auto* xlateTracksCk = mainWindow.findChild("xlateTracksCk"); + QVERIFY(xlateTracksCk != nullptr); + xlateTracksCk->setCheckState(Qt::Unchecked); + + auto* xlateFiltersBtn = mainWindow.findChild("xlateFiltersBtn"); + QVERIFY(xlateFiltersBtn != nullptr); + runDialog(&status, &test1::FilterDialogTester, xlateFiltersBtn); + QVERIFY2(status.errorCode == 0, qPrintable(status.message)); + + auto* moreOptionButton = mainWindow.findChild("moreOptionButton"); + QVERIFY(moreOptionButton != nullptr); + runDialog(&status, &test1::AdvDialogTester, moreOptionButton); + QVERIFY2(status.errorCode == 0, qPrintable(status.message)); + + // The output file/device buttons can be set to check/unchecked, unchecked/unchecked, + // or unchecked/checked. + auto* outputFileOptBtn = mainWindow.findChild("outputFileOptBtn"); + QVERIFY(outputFileOptBtn != nullptr); + auto* outputDeviceOptBtn = mainWindow.findChild("outputDeviceOptBtn"); + QVERIFY(outputDeviceOptBtn != nullptr); + outputFileOptBtn->click(); + outputFileOptBtn->click(); + QVERIFY(outputFileOptBtn->isChecked()); + + auto* outputFormatCombo = mainWindow.findChild("outputFormatCombo"); + QVERIFY(outputFormatCombo != nullptr); + int oidx = outputFormatCombo->findText("Google Earth (Keyhole) Markup Language"); + QVERIFY(oidx >= 0); + // FIXME: not keyboard/mouse simulation + outputFormatCombo->setCurrentIndex(oidx); + + auto* outputFileNameText = mainWindow.findChild("outputFileNameText"); + QVERIFY(outputFileNameText != nullptr); + outputFileNameText->insert("junk"); + + auto* outputOptionsBtn = mainWindow.findChild("outputOptionsBtn"); + QVERIFY(outputOptionsBtn != nullptr); + runDialog(&status, &test1::OptionsDialogTester, outputOptionsBtn); + QVERIFY2(status.errorCode == 0, qPrintable(status.message)); + + auto* buttonBox = mainWindow.findChild("buttonBox"); + QVERIFY(buttonBox != nullptr); + auto* mainwindowOK = buttonBox->button(QDialogButtonBox::Ok); + QVERIFY(mainwindowOK != nullptr); + runDialog(&status, &test1::GMapDialogTester, mainwindowOK); + QVERIFY2(status.errorCode == 0, qPrintable(status.message)); + + auto* outputWindow = mainWindow.findChild("outputWindow"); + QVERIFY(outputWindow != nullptr); + QTest::qWait(2000); + qDebug() << outputWindow->toPlainText(); + QString output = outputWindow->toPlainText().replace(QRegularExpression("\\S*GPSBabel-TEST\\......."), "GPSBabel-TEST.XXXXXX"); + QCOMPARE(output, + QString("gpsbabel -w -i gpx -f reference/bounds-test.gpx -x sort,shortname -x track,start=20220517020304 -x duplicate,shortname -x simplify,count=100 -o kml,prec=3 -F junk -o gpx -F GPSBabel-TEST.XXXXXX\n\nTranslation successful")); + + auto* mainwindowClose = buttonBox->button(QDialogButtonBox::Close); + QVERIFY(mainwindowClose != nullptr); + runDialog(&status, &test1::DonateDialogTester, mainwindowClose); + QVERIFY2(status.errorCode == 0, qPrintable(status.message)); +} + +QWidget* test1::selectFilterWidget(QWidget* filterDialog, const QString& dialogName, const QString& widgetName) +{ + auto* filterList = filterDialog->findChild("filterList"); + if (filterList == nullptr) { + return nullptr; + } + + auto itemlist = filterList->findItems(dialogName, Qt::MatchExactly); + if (itemlist.count() != 1) { + return nullptr; + } + + auto* item = itemlist.at(0); + // FIXME: not keyboard/mouse simulation + // It isn't clear how to simulate clicking to make the item checked. + item->setCheckState(Qt::Checked); + QRect rect = filterList->visualItemRect(item); + QTest::mouseClick(filterList->viewport(), Qt::LeftButton, Qt::NoModifier, rect.center()); + + auto* widgetStack = filterDialog->findChild("widgetStack"); + if (widgetStack == nullptr) { + return nullptr; + } + + auto* currentWidget = widgetStack->currentWidget(); + if (currentWidget == nullptr || (currentWidget->objectName() != widgetName)) { + return nullptr; + } + + return currentWidget; +} + +// QVERIFY and many other Q test macros can only be called from a test function +// invoked by the test framework. +#define DIALOGVERIFY(status, widget, condition, msg) \ + do { \ + if (static_cast(condition)) { \ + (*status).errorCode = 0; \ + } else { \ + (*status).errorCode = 1; \ + (*status).message = QString(msg); \ + if (widget != nullptr) { \ + qobject_cast(widget)->done(-1); \ + } \ + return; \ + } \ + } while (false) + +void test1::AboutDialogTester(dialogStatus* status) +{ + qDebug() << "About Dialog Tester"; + QTest::qWait(1000); + auto* widget = QApplication::activeModalWidget(); + if (widget != nullptr) { + if (widget->inherits("AboutDlg")) { + //DIALOGVERIFY(status, widget, false, "AboutDlg: test failure"); + //widget->dumpObjectTree(); + auto* buttonBox = widget->findChild("buttonBox"); + DIALOGVERIFY(status, widget, buttonBox != nullptr, "AboutDlg: can't find buttonBox"); + auto* optOK = buttonBox->button(QDialogButtonBox::Ok); + DIALOGVERIFY(status, widget, optOK != nullptr, "AboutDlg: can't find OK button"); + QTest::mouseClick(optOK, Qt::LeftButton); + } else { + DIALOGVERIFY(status, widget, false, "Expected AboutDlg, but someting else is the Modal Widget"); + } + } + qDebug() << "About Dialog Tester Exiting"; +} + +void test1::AdvDialogTester(dialogStatus* status) +{ + qDebug() << "Adv Dialog Tester"; + QTest::qWait(1000); + auto* widget = QApplication::activeModalWidget(); + if (widget != nullptr) { + if (widget->inherits("AdvDlg")) { + //widget->dumpObjectTree(); + auto* previewGmap = widget->findChild("previewGmap"); + DIALOGVERIFY(status, widget, previewGmap != nullptr, "AdvDlg: can't find previewGmap"); + QTest::mouseClick(previewGmap, Qt::LeftButton); + + auto* formatButton = widget->findChild("formatButton"); + DIALOGVERIFY(status, widget, formatButton != nullptr, "AdvDlg: can't find formatButton"); + dialogStatus msgStatus; + runDialog(&msgStatus, &test1::QMessageBoxDialogTester, formatButton); + DIALOGVERIFY(status, widget, msgStatus.errorCode == 0, qPrintable(msgStatus.message)); + + auto* advButtonBox = widget->findChild("buttonBox"); + DIALOGVERIFY(status, widget, advButtonBox != nullptr, "AdvDlg: can't find buttonBox"); + auto* advOK = advButtonBox->button(QDialogButtonBox::Ok); + DIALOGVERIFY(status, widget, advOK != nullptr, "AdvDlg: can't find OK button"); + QTest::mouseClick(advOK, Qt::LeftButton); + } else { + DIALOGVERIFY(status, widget, false, "Expected AdvDlg, but someting else is the Modal Widget"); + } + } + qDebug() << "Adv Dialog Tester Exiting"; +} + +void test1::DonateDialogTester(dialogStatus* status) +{ + qDebug() << "Donate Dialog Tester"; + QTest::qWait(1000); + auto* widget = QApplication::activeModalWidget(); + if (widget != nullptr) { + if (widget->inherits("Donate")) { + //widget->dumpObjectTree(); + auto* dismissButton = widget->findChild("dismissButton"); + DIALOGVERIFY(status, widget, dismissButton != nullptr, "Donate: can't find dismissButton"); + QTest::mouseClick(dismissButton, Qt::LeftButton); + } else { + DIALOGVERIFY(status, widget, false, "Expected Donate, but someting else is the Modal Widget"); + } + } + qDebug() << "Donate Dialog Tester Exiting"; +} + +void test1::FilterDialogTester(dialogStatus* status) +{ + qDebug() << "Filter Dialog Tester"; + QTest::qWait(1000); + auto* widget = QApplication::activeModalWidget(); + if (widget != nullptr) { + if (widget->inherits("FilterDialog")) { + //widget->dumpObjectTree(); + auto* reset = widget->findChild("resetButton"); + DIALOGVERIFY(status, widget, reset != nullptr, "FilterDialog: can't find resetButton"); + //DIALOGVERIFY(status, widget, false, "test death"); + dialogStatus msgStatus; + runDialog(&msgStatus, &test1::QMessageBoxDialogTester, reset); + DIALOGVERIFY(status, widget, msgStatus.errorCode == 0, qPrintable(msgStatus.message)); + + QWidget* currentWidget = selectFilterWidget(widget, "Miscellaneous", "MiscFltWidget"); + DIALOGVERIFY(status, widget, currentWidget != nullptr, "FilterDialog: can't find MiscFltWidget in Miscellaneous dialog."); + + auto* sortWptCheck = currentWidget->findChild("sortWptCheck"); + DIALOGVERIFY(status, widget, sortWptCheck != nullptr, "FilterDialog: cant find sortWptCheck"); + QTest::mouseClick(sortWptCheck, Qt::LeftButton); + + auto* sortWptBy = currentWidget->findChild("sortWptBy"); + DIALOGVERIFY(status, widget, sortWptBy != nullptr, "FilterDialog: can't find sortWptBy"); + int cbidx = sortWptBy->findText("Name"); + DIALOGVERIFY(status, widget, cbidx >= 0, ""); + sortWptBy->setCurrentIndex(cbidx); + + QTest::qWait(2000); + + currentWidget = selectFilterWidget(widget, "Waypoints", "WayPtsWidget"); + DIALOGVERIFY(status, widget, currentWidget != nullptr, "FilterDialog: can't find WaypPtsWidget in Waypoints dialog."); + + auto* duplicatesCheck = currentWidget->findChild("duplicatesCheck"); + DIALOGVERIFY(status, widget, duplicatesCheck != nullptr, "FilterDialog: can't find duplicatesCheck"); + QTest::mouseClick(duplicatesCheck, Qt::LeftButton); + + QTest::qWait(2000); + + currentWidget = selectFilterWidget(widget, "Routes & Tracks", "RtTrkWidget"); + DIALOGVERIFY(status, widget, currentWidget != nullptr, "FilterDialog: can't find RtTrkWidget in Routes & Tracks dialog."); + + auto* simplifyCheck = currentWidget->findChild("simplifyCheck"); + DIALOGVERIFY(status, widget, simplifyCheck != nullptr, "FilterDialog: can't find simplifyCheck"); + QTest::mouseClick(simplifyCheck, Qt::LeftButton); + + QTest::qWait(2000); + + currentWidget = selectFilterWidget(widget, "Tracks", "TrackWidget"); + DIALOGVERIFY(status, widget, currentWidget != nullptr, "FilterDialog: can't find TrackWidget in Tracks dialog."); + + auto* startCheck = currentWidget->findChild("startCheck"); + DIALOGVERIFY(status, widget, startCheck != nullptr, "FilterDialog: can't find startCheck"); + auto* stopCheck = currentWidget->findChild("stopCheck"); + DIALOGVERIFY(status, widget, stopCheck != nullptr, "FilterDialog: can't find stopCheck"); + + QTest::mouseClick(startCheck, Qt::LeftButton); + + auto* utc = currentWidget->findChild("utc"); + DIALOGVERIFY(status, widget, utc != nullptr, "FilterDialog: can't find utc"); + auto* localTime = currentWidget->findChild("localTime"); + DIALOGVERIFY(status, widget, localTime != nullptr, "FilterDialog: can't find localTime"); + auto* startEdit = currentWidget->findChild("startEdit"); + DIALOGVERIFY(status, widget, startEdit != nullptr, "FilterDialog: can't find startEdit"); + auto* stopEdit = currentWidget->findChild("stopEdit"); + DIALOGVERIFY(status, widget, stopEdit != nullptr, "FilterDialog: can't find stopEdit"); + +#if 0 + // It seems to require searching to find the clickable area of a radio button. + QTest::mouseClick(utc, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(0, utc->height()/2)); +#else + utc->click(); +#endif + DIALOGVERIFY(status, widget, utc->isChecked(), "FilterDialog: difficulty checking utc"); + DIALOGVERIFY(status, widget, !localTime->isChecked(), "FilterDialog: difficulty checking utc"); + DIALOGVERIFY(status, widget, startEdit->timeSpec() == Qt::UTC, "FilterDialog: difficulty checking utc"); + DIALOGVERIFY(status, widget, stopEdit->timeSpec() == Qt::UTC, "FilterDialog: difficulty checking utc"); + QTest::qWait(2000); + +#if 0 + // It seems to require searching to find the clickable area of a radio button. + QTest::mouseClick(localTime, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(0, localTime->height()/2)); +#else + localTime->click(); +#endif + DIALOGVERIFY(status, widget, !utc->isChecked(), "FilterDialog: difficulty checking localTime"); + DIALOGVERIFY(status, widget, localTime->isChecked(), "FilterDialog: difficulty checking localTime"); + DIALOGVERIFY(status, widget, startEdit->timeSpec() == Qt::LocalTime, "FilterDialog: difficulty checking localTime"); + DIALOGVERIFY(status, widget, stopEdit->timeSpec() == Qt::LocalTime, "FilterDialog: difficulty checking localTime"); + QTest::qWait(2000); + + // FIXME: not keyboard/mouse simulation + // In Qt 5 "When setting this property the timespec of the QDateTimeEdit remains the same and the timespec of the new QDateTime is ignored." + // In Qt 6 "When setting this property, the new QDateTime is converted to the timespec of the QDateTimeEdit, which thus remains unchanged." + // To cover up this change in behavior convert to the QDateTimeEdit timeSpec manually. + auto startDT = QDateTime::fromString("2022-05-17T02:03:04Z", Qt::ISODate).toTimeSpec(startEdit->timeSpec()); + startEdit->setDateTime(startDT); + DIALOGVERIFY(status, widget, startEdit->timeSpec() == Qt::LocalTime, "FilterDialog: unexpected timeSpec of startEdit"); + DIALOGVERIFY(status, widget, startEdit->dateTime() == startDT.toTimeSpec(Qt::LocalTime), "FilterDialog: unexpected startTime set"); + QTest::qWait(2000); + + auto* filterButtonBox = widget->findChild("buttonBox"); + DIALOGVERIFY(status, widget, filterButtonBox != nullptr, "FilterDialog: can't find buttonBox"); + auto* filterOK = filterButtonBox->button(QDialogButtonBox::Ok); + DIALOGVERIFY(status, widget, filterOK != nullptr, "FilterDialog: can't find OK button"); + QTest::mouseClick(filterOK, Qt::LeftButton); + } else { + DIALOGVERIFY(status, widget, false, "Expected FilterDialog, but someting else is the Modal Widget"); + } + } + qDebug() << "Filter Dialog Tester Exiting"; +} + +void test1::GMapDialogTester(dialogStatus* status) +{ + qDebug() << "GMap Dialog Tester"; + QTest::qWait(2000); + // Why can't we use activeModalWidget()? + // Gnome application "is ready" notification? + qDebug() << QApplication::activeModalWidget(); + const auto topLevelWidgets = QApplication::topLevelWidgets(); + QWidget* gmapwidget = nullptr; + for (auto* widget : topLevelWidgets) { + qDebug() << "top level widget" << widget; + if (widget->inherits("GMapDialog")) { + gmapwidget = widget; + break; + } + } + // We may have to wait for the CLI to run (ProcessWaitDialog). + if (gmapwidget == nullptr) { + qDebug() << "Waiting for GMapDialog"; + QTimer::singleShot(200, this, &test1::dialogcb); + return; + } + auto* gmapButtonBox = gmapwidget->findChild("buttonBox"); + DIALOGVERIFY(status, gmapwidget, gmapButtonBox != nullptr, "GMapDialog: can't find buttonBox"); + auto* gmapClose = gmapButtonBox->button(QDialogButtonBox::Close); + DIALOGVERIFY(status, gmapwidget, gmapClose != nullptr, "GMapDialog: can't find Close button"); + QTest::mouseClick(gmapClose, Qt::LeftButton); + qDebug() << "GMap Dialog Tester Exiting"; +} + +void test1::OptionsDialogTester(dialogStatus* status) +{ + qDebug() << "Options Dialog Tester"; + QTest::qWait(1000); + auto* widget = QApplication::activeModalWidget(); + if (widget != nullptr) { + if (widget->inherits("OptionsDlg")) { + //widget->dumpObjectTree(); + auto* precOpt = widget->findChild("kml_prec"); + DIALOGVERIFY(status, widget, precOpt != nullptr, "OptionsDlg: can't find kml_prec QCheckBox"); + QTest::mouseClick(precOpt, Qt::LeftButton); + auto* precValue = widget->findChild("kml_prec"); + DIALOGVERIFY(status, widget, precValue != nullptr, "OptionsDlg: can't find kml_prec QLineEdit"); +#ifdef Q_OS_MACOS + QTest::keyClick(precValue, Qt::Key_A, Qt::MetaModifier); // select all + QTest::keyClick(precValue, Qt::Key_Delete); +#else + QTest::keyClick(precValue, Qt::Key_Home); // move to beginning of line + QTest::keyClick(precValue, Qt::Key_K, Qt::ControlModifier); // delete to end of line +#endif + QTest::keyClicks(precValue, "3"); + QTest::qWait(1000); + + auto* optButtonBox = widget->findChild("buttonBox"); + DIALOGVERIFY(status, widget, optButtonBox != nullptr, "OptionsDlg: can't find buttonBox"); + auto* optOK = optButtonBox->button(QDialogButtonBox::Ok); + DIALOGVERIFY(status, widget, optOK != nullptr, "OptionsDlg: can't find OK button"); + QTest::mouseClick(optOK, Qt::LeftButton); + } else { + DIALOGVERIFY(status, widget, false, "Expected OptionsDlg, but someting else is the Modal Widget"); + } + } + qDebug() << "Options Dialog Tester Exiting"; +} + +void test1::QMessageBoxDialogTester(dialogStatus* status) +{ + qDebug() << "QMessageBox Dialog Tester"; + QTest::qWait(1000); + auto* widget = QApplication::activeModalWidget(); + if (widget != nullptr) { + if (widget->inherits("QMessageBox")) { + //widget->dumpObjectTree(); + QMessageBox* mb = qobject_cast(widget); + auto* yes = mb->button(QMessageBox::Yes); + DIALOGVERIFY(status, widget, yes != nullptr, "QMessageBoxDialog: can't find Yes button"); + QTest::mouseClick(yes, Qt::LeftButton); + } else { + DIALOGVERIFY(status, widget, false, "Expected QMessagBox, but someting else is the Modal Widget"); + } + } + qDebug() << "QMessageBox Dialog Tester Exiting"; +} + +void test1::PreferencesDialogTester(dialogStatus* status) +{ + qDebug() << "Preferences Dialog Tester"; + QTest::qWait(1000); + auto* widget = QApplication::activeModalWidget(); + if (widget != nullptr) { + if (widget->inherits("Preferences")) { + //widget->dumpObjectTree(); + bool tabWidgetFound = false; + auto* tabWidget = widget->findChild("tabWidget"); + DIALOGVERIFY(status, widget, tabWidget != nullptr, "PreferencesDialog; can't find QTabWidget"); + for (int idx = 0; idx < tabWidget->count(); ++idx) { + if (tabWidget->tabText(idx) == "Formats") { + tabWidget->setCurrentIndex(idx); + tabWidgetFound = true; + break; + } + } + DIALOGVERIFY(status, widget, tabWidgetFound, "PreferencesDialog: can't find Formats tab"); + QTest::qWait(1000); + + auto* enableAllButton = tabWidget->findChild("enableAllButton"); + DIALOGVERIFY(status, widget, enableAllButton != nullptr, "PreferencesDialog: can't find enableAllButton"); + QTest::mouseClick(enableAllButton, Qt::LeftButton); + + auto* buttonBox = widget->findChild("buttonBox"); + DIALOGVERIFY(status, widget, buttonBox != nullptr, "PreferncesDialog: can't find buttonBox"); + auto* optOK = buttonBox->button(QDialogButtonBox::Ok); + DIALOGVERIFY(status, widget, optOK != nullptr, "PreferencesDialog: can't find OK button"); + QTest::mouseClick(optOK, Qt::LeftButton); + } else { + DIALOGVERIFY(status, widget, false, "Expected Preferences, but someting else is the Modal Widget"); + } + } + qDebug() << "Preferences Dialog Tester Exiting"; +} +QTEST_MAIN(test1) + +//#include "main.moc" diff --git a/gui/test_main.h b/gui/test_main.h new file mode 100644 index 000000000..43676c769 --- /dev/null +++ b/gui/test_main.h @@ -0,0 +1,52 @@ +#include // for QObject, Q_OBJECT, slots +#include // for QString +#include // for QWidget + + +class test1 : public QObject +{ + Q_OBJECT + +public: + /* Types */ + + struct dialogStatus { + int errorCode; + QString message; + }; + + using dialog_cb = void (test1::*)(dialogStatus*); + + /* Special Member Functions */ + + test1(); + ~test1(); + +private: + /* Member Functions */ + + // don't declare these slots so they won't be run as tests. + static QWidget* selectFilterWidget(QWidget* filterDialog, const QString& dialogName, const QString& widgetName); + void dialogcb(); + void runDialog(dialogStatus*, dialog_cb, QWidget*); + + void AboutDialogTester(dialogStatus* status); + void AdvDialogTester(dialogStatus* status); + void DonateDialogTester(dialogStatus* status); + void FilterDialogTester(dialogStatus* status); + void GMapDialogTester(dialogStatus* status); + void OptionsDialogTester(dialogStatus* status); + void QMessageBoxDialogTester(dialogStatus* status); + void PreferencesDialogTester(dialogStatus* status); + +private slots: + /* Member Functions */ + + void initTestCase(); + void cleanupTestCase(); + void test_case1(); + +private: + dialogStatus* status_{nullptr}; + dialog_cb dialogHandler_{nullptr}; +}; From eab1d8eb9bc644131600b66e1c05f2ce97e79bdb Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Fri, 2 Sep 2022 13:32:05 -0600 Subject: [PATCH 2/3] get windows gui test working --- CMakeLists.txt | 3 ++- gui/CMakeLists.txt | 16 ++++++---------- gui/test_main.cc | 15 +++++++++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a8198234..9fb7b39b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ project(gpsbabel LANGUAGES C CXX VERSION ${GB.VERSION}) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) +enable_testing() + # Do this after we set up common variables but before creating other # variables that will be inherited. add_subdirectory(gui) @@ -439,7 +441,6 @@ list(SORT TESTS) if(UNIX) # This test only works if the pwd is top level source dir due to the # file name getting embedded in the file nonexistent.err. - enable_testing() foreach(TESTNAME IN LISTS TESTS) add_test(NAME test-${TESTNAME} COMMAND ${CMAKE_SOURCE_DIR}/testo -p $ ${TESTNAME} diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 94840e53a..1c14b0e02 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -26,7 +26,8 @@ add_library(${LIBTARGET} STATIC) set(CMAKE_AUTOMOC OFF) set(CMAKE_AUTOUIC OFF) add_executable(${TARGET} WIN32 MACOSX_BUNDLE main.cc) -add_executable(${TESTTARGET} WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL test_main.cc test_main.h) +# Don't set WIN32 on TESTTARGET, we want to see qDebug messages. +add_executable(${TESTTARGET} MACOSX_BUNDLE EXCLUDE_FROM_ALL test_main.cc test_main.h) # Find the QtCore library find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) @@ -216,12 +217,7 @@ add_custom_command( VERBATIM ) -if(UNIX) - enable_testing() - add_test(NAME test-gui - COMMAND ${TESTTARGET} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) -endif() - - +add_test(NAME test-gui + COMMAND ${TESTTARGET} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) diff --git a/gui/test_main.cc b/gui/test_main.cc index c9b2bcb9b..894b11159 100644 --- a/gui/test_main.cc +++ b/gui/test_main.cc @@ -427,7 +427,6 @@ void test1::FilterDialogTester(dialogStatus* status) void test1::GMapDialogTester(dialogStatus* status) { qDebug() << "GMap Dialog Tester"; - QTest::qWait(2000); // Why can't we use activeModalWidget()? // Gnome application "is ready" notification? qDebug() << QApplication::activeModalWidget(); @@ -446,6 +445,15 @@ void test1::GMapDialogTester(dialogStatus* status) QTimer::singleShot(200, this, &test1::dialogcb); return; } + // Wait for the page to load + // we monitor the overrideCursor which map sets in the constructor and + // resets when loadFinished is emitted; + auto startload = QTime::currentTime(); + (void) QTest::qWaitFor([]() {return qApp->overrideCursor() == nullptr;}, + 30000); + auto stopload = QTime::currentTime(); + qDebug() << "page load(s):" << startload.msecsTo(stopload)/1000.0; + QTest::qWait(3000); auto* gmapButtonBox = gmapwidget->findChild("buttonBox"); DIALOGVERIFY(status, gmapwidget, gmapButtonBox != nullptr, "GMapDialog: can't find buttonBox"); auto* gmapClose = gmapButtonBox->button(QDialogButtonBox::Close); @@ -469,11 +477,10 @@ void test1::OptionsDialogTester(dialogStatus* status) DIALOGVERIFY(status, widget, precValue != nullptr, "OptionsDlg: can't find kml_prec QLineEdit"); #ifdef Q_OS_MACOS QTest::keyClick(precValue, Qt::Key_A, Qt::MetaModifier); // select all - QTest::keyClick(precValue, Qt::Key_Delete); #else - QTest::keyClick(precValue, Qt::Key_Home); // move to beginning of line - QTest::keyClick(precValue, Qt::Key_K, Qt::ControlModifier); // delete to end of line + QTest::keyClick(precValue, Qt::Key_A, Qt::ControlModifier); // select all #endif + QTest::keyClick(precValue, Qt::Key_Delete); QTest::keyClicks(precValue, "3"); QTest::qWait(1000); From 4be85e9da5c5b12a859e5486785bce6615deaf03 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Mon, 10 Oct 2022 14:04:09 -0700 Subject: [PATCH 3/3] build gui tester before running ctest. seems like we need a dependency on gpsbable, gpsbabefe_test. --- tools/ci_script_osx.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/ci_script_osx.sh b/tools/ci_script_osx.sh index e6ca180b6..c58b93d44 100755 --- a/tools/ci_script_osx.sh +++ b/tools/ci_script_osx.sh @@ -38,12 +38,14 @@ case "${GENERATOR[1]}" in Xcode | "Ninja Multi-Config") cmake "${SOURCE_DIR}" -DCMAKE_OSX_ARCHITECTURES=${ARCHS} -DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOY_TARGET} "${GENERATOR[@]}" cmake --build . --config Release + cmake --build . --config Release --target gpsbabelfe_test ctest -C Release cmake --build . --config Release --target package_app ;; *) cmake "${SOURCE_DIR}" -DCMAKE_OSX_ARCHITECTURES=${ARCHS} -DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOY_TARGET} -DCMAKE_BUILD_TYPE=Release "${GENERATOR[@]}" cmake --build . + cmake --build . --target gpsbabelfe_test ctest cmake --build . --target package_app ;;