diff --git a/.gitignore b/.gitignore index c56e1ff..670981c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .idea build dep-libs +CMakeLists.txt.user diff --git a/examples/Basic/main.qml b/examples/Basic/main.qml index 153f204..6c981de 100644 --- a/examples/Basic/main.qml +++ b/examples/Basic/main.qml @@ -21,13 +21,13 @@ Window { } Button { - objectName: "Button_1" text: "Press Me" MouseArea { + objectName: "Button_1" anchors.fill: parent acceptedButtons: Qt.AllButtons - onClicked: + onClicked: { if(mouse.button & Qt.RightButton) resultsView.appendText("Button 1 right clicked") @@ -37,13 +37,13 @@ Window { } } Button { - objectName: "Button_2" text: "Or Click Me" MouseArea { + objectName: "Button_2" anchors.fill: parent acceptedButtons: Qt.AllButtons - onClicked: + onClicked: { if(mouse.button & Qt.RightButton) resultsView.appendText("Button 2 right clicked") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b5da580..6e8768e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(GTest) add_subdirectory(ListGridView) add_subdirectory(RemoteCtrl) add_subdirectory(RepeaterLoader) +add_subdirectory(QtTest) diff --git a/examples/GTest/main.qml b/examples/GTest/main.qml index 153f204..413d3f8 100644 --- a/examples/GTest/main.qml +++ b/examples/GTest/main.qml @@ -21,36 +21,36 @@ Window { } Button { - objectName: "Button_1" text: "Press Me" - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.AllButtons - - onClicked: - { - if(mouse.button & Qt.RightButton) - resultsView.appendText("Button 1 right clicked") - else - resultsView.appendText("Button 1 clicked") - } - } + MouseArea { + objectName: "Button_1" + anchors.fill: parent + acceptedButtons: Qt.AllButtons + + onClicked: + { + if(mouse.button & Qt.RightButton) + resultsView.appendText("Button 1 right clicked") + else + resultsView.appendText("Button 1 clicked") + } + } } Button { - objectName: "Button_2" text: "Or Click Me" - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.AllButtons + MouseArea { + objectName: "Button_2" + anchors.fill: parent + acceptedButtons: Qt.AllButtons - onClicked: - { - if(mouse.button & Qt.RightButton) - resultsView.appendText("Button 2 right clicked") - else - resultsView.appendText("Button 2 clicked") - } - } + onClicked: + { + if(mouse.button & Qt.RightButton) + resultsView.appendText("Button 2 right clicked") + else + resultsView.appendText("Button 2 clicked") + } + } } } diff --git a/examples/QtTest/CMakeLists.txt b/examples/QtTest/CMakeLists.txt new file mode 100644 index 0000000..eedda5a --- /dev/null +++ b/examples/QtTest/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.16) + +project(QtTestExample VERSION 1.0 LANGUAGES CXX) + +enable_testing() + +set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(AnyRPC REQUIRED) +find_package(Qt${SPIX_QT_MAJOR} REQUIRED COMPONENTS Test Quick) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include_directories(${AnyRPC_INCLUDE_DIRS}) + +add_executable(QtTestExample tst_test.cpp) +add_test(NAME QtTestExample COMMAND QtTestExample) + +target_link_libraries(QtTestExample PRIVATE + Qt${SPIX_QT_MAJOR}::Test + Spix + + PRIVATE + AnyRPC::anyrpc +) + diff --git a/examples/QtTest/tst_test.cpp b/examples/QtTest/tst_test.cpp new file mode 100644 index 0000000..8b6d5ec --- /dev/null +++ b/examples/QtTest/tst_test.cpp @@ -0,0 +1,145 @@ +#include + +#include "anyrpc/anyrpc.h" + +#include + +using namespace anyrpc; + +/** + * @brief Example using QtTest for running test cases against a remote server + * It also contains some handy helper functions to call remote methods. + * + * Run e.g. the RemoteCtrl application as the application under test + */ +class QtTestExample : public QObject { + Q_OBJECT + + std::shared_ptr client; + +public: + QtTestExample(); + ~QtTestExample(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void test_case1(); + + void waitFor(int time) + { + Value paramsWait; + Value resultWait; + paramsWait[0] = time; + client->Call("wait", paramsWait, resultWait); + } + + void mouseClick(std::string path) + { + Value paramsMouse; + Value resultMouse; + paramsMouse[0] = path; + client->Call("mouseClick", paramsMouse, resultMouse); + waitFor(500); + } + + void screenshot(std::string path, std::string filepath, bool wait = true, bool compare = true) + { + Value params; + Value result; + params[0] = path; + params[1] = filepath; + client->Call("takeScreenshot", params, result); + } + + void setProperty(std::string path, std::string field, std::string value) + { + Value params; + Value result; + params[0] = path; + params[1] = field; + params[2] = value; + client->Call("setStringProperty", params, result); + } + + QString getProperty(std::string path, std::string field) + { + Value params; + Value result; + params[0] = path; + params[1] = field; + client->Call("getStringProperty", params, result); + + return QString::fromStdString(result.GetString()); + } + + void invokeMethod(std::string path, std::string field, const std::vector functionParameter) + { + Value params; + Value result; + + Value parameters; + parameters.SetArray(functionParameter.size()); + for (int i = 0; i < functionParameter.size(); ++i) { + parameters[i] = functionParameter.at(i); + } + + params[0] = path; + params[1] = field; + params[2] = parameters; + client->Call("invokeMethod", params, result); + } +}; + +QtTestExample::QtTestExample() +{ +} + +QtTestExample::~QtTestExample() +{ +} + +void QtTestExample::initTestCase() +{ + client = std::make_shared(); + client->SetServer("127.0.0.1", 9000); + client->SetTimeout(10000); +} + +void QtTestExample::cleanupTestCase() +{ +} + +void QtTestExample::test_case1() +{ + setProperty("mainWindow/results", "text", ""); + + mouseClick("mainWindow/Button_1"); + waitFor(500); + mouseClick("mainWindow/Button_2"); + waitFor(500); + + // in the "old" variant, this call also triggers mouseArea_2 + // this is somehow correct, because the mouse area is the top component, + // but sometimes we do not want to trigger the top most component in test cases + mouseClick("mainWindow/Button_3"); + waitFor(500); + mouseClick("mainWindow/Button_4"); + waitFor(500); + + mouseClick("mainWindow/mouseArea_1"); + waitFor(500); + mouseClick("mainWindow/mouseArea_2"); + waitFor(500); + + auto resultText = getProperty("mainWindow/results", "text"); + std::cout << resultText.toStdString() << std::endl; + + QCOMPARE(resultText, + "Button 1 clicked\nButton 2 clicked\nButton 3 clicked\nButton 4 clicked\nMouseArea 1 clicked\nMouseArea 2 " + "clicked"); +} + +QTEST_APPLESS_MAIN(QtTestExample) + +#include "tst_test.moc" diff --git a/examples/RemoteCtrl/main.cpp b/examples/RemoteCtrl/main.cpp index 8489093..4219756 100644 --- a/examples/RemoteCtrl/main.cpp +++ b/examples/RemoteCtrl/main.cpp @@ -9,8 +9,6 @@ #include #include -#include - int main(int argc, char* argv[]) { // Init Qt Qml Application diff --git a/examples/RemoteCtrl/main.qml b/examples/RemoteCtrl/main.qml index c13bea1..8045ea0 100644 --- a/examples/RemoteCtrl/main.qml +++ b/examples/RemoteCtrl/main.qml @@ -11,34 +11,63 @@ Window { title: qsTr("Spix Example") color: "#111111" - RowLayout { + ColumnLayout { + id: columnLayout - anchors { - top: parent.top - left: parent.left - topMargin: 5 - leftMargin: 5 - } + RowLayout { + id: rowLayout - Button { - objectName: "Button_1" - text: "Press Me" - onClicked: resultsView.appendText("Button 1 clicked") - } - Button { - objectName: "Button_2" - text: "Or Click Me" - onClicked: resultsView.appendText("Button 2 clicked") + Layout.preferredHeight: 200 + + Button { + objectName: "Button_1" + text: "Press Me" + onClicked: resultsView.appendText("Button 1 clicked") + } + Button { + objectName: "Button_2" + text: "Or Click Me" + onClicked: resultsView.appendText("Button 2 clicked") + } + + Rectangle { + id: rectangle + color: "lightgray" + + Layout.preferredWidth: 200 + Layout.preferredHeight: 100 + + MouseArea { + id: mouseArea_1 + anchors.fill: parent + + onClicked: resultsView.appendText("MouseArea 1 clicked") + } + + RowLayout { + Button { + objectName: "Button_3" + text: "Press Me" + onClicked: resultsView.appendText("Button 3 clicked") + } + Button { + objectName: "Button_4" + text: "Or Click Me" + onClicked: resultsView.appendText("Button 4 clicked") + } + } + + MouseArea { + id: mouseArea_2 + anchors.fill: parent + + onClicked: resultsView.appendText("MouseArea 2 clicked") + } + } } - } - ResultsView { - id: resultsView - anchors { - top: parent.verticalCenter - left: parent.left - right: parent.right - bottom: parent.bottom + ResultsView { + id: resultsView } } } diff --git a/examples/RepeaterLoader/main.cpp b/examples/RepeaterLoader/main.cpp index 8d12702..7012443 100644 --- a/examples/RepeaterLoader/main.cpp +++ b/examples/RepeaterLoader/main.cpp @@ -20,7 +20,7 @@ class MyTests : public spix::TestServer { std::cout << "Error: Item should not be visible at launch." << std::endl; } - mouseClick("mainWindow/ItemButtons/Item_0"); + mouseClick("mainWindow/ItemButtons/Item_0/mouseArea"); wait(std::chrono::milliseconds(500)); if (!existsAndVisible("mainWindow/ItemDisplayLoader/textItem")) { @@ -29,7 +29,7 @@ class MyTests : public spix::TestServer { // 'ItemButtons' is not required in the path, // as 'gridItem_0' is unique within 'mainWindow' - mouseClick("mainWindow/Item_1"); + mouseClick("mainWindow/Item_1/mouseArea"); wait(std::chrono::milliseconds(500)); auto result = getStringProperty("mainWindow/ItemDisplayLoader/textItem", "text"); @@ -37,11 +37,11 @@ class MyTests : public spix::TestServer { std::cout << "Error: Wrong item displayed." << std::endl; } - mouseClick("mainWindow/ItemButtons/Item_2"); + mouseClick("mainWindow/ItemButtons/Item_2/mouseArea"); wait(std::chrono::milliseconds(500)); - mouseClick("mainWindow/ItemButtons/Item_0"); + mouseClick("mainWindow/ItemButtons/Item_0/mouseArea"); wait(std::chrono::milliseconds(500)); - mouseClick("mainWindow/ItemButtons/Item_3"); + mouseClick("mainWindow/ItemButtons/Item_3/mouseArea"); wait(std::chrono::milliseconds(100)); result = getStringProperty("mainWindow/ItemDisplayLoader/textItem", "text"); diff --git a/examples/RepeaterLoader/main.qml b/examples/RepeaterLoader/main.qml index b02ad99..40b028a 100644 --- a/examples/RepeaterLoader/main.qml +++ b/examples/RepeaterLoader/main.qml @@ -41,6 +41,7 @@ Window { MouseArea { + id: mouseArea anchors.fill: parent onClicked: itemDisplayLoader.source = "show_" + modelData + ".qml" } diff --git a/examples/RepeaterLoader/main_gtest.cpp b/examples/RepeaterLoader/main_gtest.cpp index 7806fbf..e94bdbc 100644 --- a/examples/RepeaterLoader/main_gtest.cpp +++ b/examples/RepeaterLoader/main_gtest.cpp @@ -46,7 +46,7 @@ TEST(RepeaterLoaderGTest, RepeaterLoaderTest) EXPECT_FALSE(srv->existsAndVisible("mainWindow/ItemDisplayLoader/textItem")) << "Error: Item should not be visible at launch."; - srv->mouseClick("mainWindow/ItemButtons/Item_0"); + srv->mouseClick("mainWindow/ItemButtons/Item_0/mouseArea"); srv->wait(std::chrono::milliseconds(500)); EXPECT_TRUE(srv->existsAndVisible("mainWindow/ItemDisplayLoader/textItem")) @@ -54,18 +54,18 @@ TEST(RepeaterLoaderGTest, RepeaterLoaderTest) // 'ItemButtons' is not required in the path, // as 'gridItem_0' is unique within 'mainWindow' - srv->mouseClick("mainWindow/Item_1"); + srv->mouseClick("mainWindow/Item_1/mouseArea"); srv->wait(std::chrono::milliseconds(500)); EXPECT_STREQ(srv->getStringProperty("mainWindow/ItemDisplayLoader/textItem", "text").c_str(), "I am a view with a pear. Trust me.") << "Error: Wrong item displayed."; - srv->mouseClick("mainWindow/ItemButtons/Item_2"); + srv->mouseClick("mainWindow/ItemButtons/Item_2/mouseArea"); srv->wait(std::chrono::milliseconds(500)); - srv->mouseClick("mainWindow/ItemButtons/Item_0"); + srv->mouseClick("mainWindow/ItemButtons/Item_0/mouseArea"); srv->wait(std::chrono::milliseconds(500)); - srv->mouseClick("mainWindow/ItemButtons/Item_3"); + srv->mouseClick("mainWindow/ItemButtons/Item_3/mouseArea"); srv->wait(std::chrono::milliseconds(100)); EXPECT_STREQ(srv->getStringProperty("mainWindow/ItemDisplayLoader/textItem", "text").c_str(), diff --git a/lib/src/Scene/Qt/QtEvents.cpp b/lib/src/Scene/Qt/QtEvents.cpp index 449b901..046e963 100644 --- a/lib/src/Scene/Qt/QtEvents.cpp +++ b/lib/src/Scene/Qt/QtEvents.cpp @@ -20,6 +20,10 @@ #include #include +// comment this to use mouse events on window level +// if uncommented, the mouse events are triggered on the given item +// #define WINDOW_MODE + namespace spix { namespace { @@ -82,11 +86,15 @@ void sendQtKeyEvent(Item* item, bool press, int keyCode, KeyModifier mod) if (!qtitem) return; - auto window = qtitem->qquickitem()->window(); - auto qtmod = getQtKeyboardModifiers(mod); auto keyEvent = new QKeyEvent(press ? QEvent::KeyPress : QEvent::KeyRelease, keyCode, qtmod); + +#ifdef WINDOW_MODE + auto window = qtitem->qquickitem()->window(); QGuiApplication::postEvent(window, keyEvent); +#else + QGuiApplication::postEvent(qtitem->qquickitem(), keyEvent); +#endif } } // namespace @@ -98,13 +106,22 @@ void QtEvents::mouseDown(Item* item, Point loc, MouseButton button) if (!window) return; + auto qtitem = dynamic_cast(item); + if (!qtitem) + return; + m_pressedMouseButtons |= button; Qt::MouseButton eventCausingButton = getQtMouseButtonValue(button); Qt::MouseButtons activeButtons = getQtMouseButtonValue(m_pressedMouseButtons); QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonPress, windowLoc, eventCausingButton, activeButtons, Qt::NoModifier); + +#ifdef WINDOW_MODE QGuiApplication::postEvent(window, event); +#else + QGuiApplication::postEvent(qtitem->qquickitem(), event); +#endif } void QtEvents::mouseUp(Item* item, Point loc, MouseButton button) @@ -114,6 +131,10 @@ void QtEvents::mouseUp(Item* item, Point loc, MouseButton button) if (!window) return; + auto qtitem = dynamic_cast(item); + if (!qtitem) + return; + #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) // Qt6 expects the mouse to be down during the event Qt::MouseButton eventCausingButton = getQtMouseButtonValue(button); @@ -128,7 +149,12 @@ void QtEvents::mouseUp(Item* item, Point loc, MouseButton button) QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonRelease, windowLoc, eventCausingButton, activeButtons, Qt::NoModifier); + +#ifdef WINDOW_MODE QGuiApplication::postEvent(window, event); +#else + QGuiApplication::postEvent(qtitem->qquickitem(), event); +#endif } void QtEvents::mouseMove(Item* item, Point loc) @@ -138,19 +164,28 @@ void QtEvents::mouseMove(Item* item, Point loc) if (!window) return; + auto qtitem = dynamic_cast(item); + if (!qtitem) + return; + Qt::MouseButton activeButtons = getQtMouseButtonValue(m_pressedMouseButtons); // Wiggle the cursor a bit. This is needed to correctly recognize drag events windowLoc.rx() -= 1; QMouseEvent* mouseMoveEvent = new QMouseEvent(QEvent::MouseMove, windowLoc, Qt::MouseButton::NoButton, activeButtons, Qt::NoModifier); - QGuiApplication::postEvent(window, mouseMoveEvent); + QGuiApplication::postEvent(qtitem->qquickitem(), mouseMoveEvent); // Wiggle the cursor a bit. This is needed to correctly recognize drag events windowLoc.rx() += 1; mouseMoveEvent = new QMouseEvent(QEvent::MouseMove, windowLoc, Qt::MouseButton::NoButton, activeButtons, Qt::NoModifier); + +#ifdef WINDOW_MODE QGuiApplication::postEvent(window, mouseMoveEvent); +#else + QGuiApplication::postEvent(qtitem->qquickitem(), mouseMoveEvent); +#endif } void QtEvents::stringInput(Item* item, const std::string& text)