tests(StatusInput): add regression test to check for qml output

Also improves on the test structure
This commit is contained in:
Stefan 2022-03-21 13:04:11 +02:00 committed by Stefan Dunca
parent b2ac2794bb
commit eae4e0f405
11 changed files with 253 additions and 13 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ sandbox/qmlcache_loader.cpp
doc/html doc/html
CMakeLists.txt.user CMakeLists.txt.user
.vscode .vscode
build/

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
project(TestStatusInputWithRegex LANGUAGES CXX) project(TestControls LANGUAGES CXX)
enable_testing() enable_testing()
@ -9,8 +9,8 @@ list(APPEND QML_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../../src/")
set(QML_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths") set(QML_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
set(QML2_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths") set(QML2_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
find_package(QT NAMES Qt6 Qt5 COMPONENTS QuickTest Qml REQUIRED) find_package(QT NAMES Qt6 Qt5 COMPONENTS QuickTest Qml Quick REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml Quick REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -24,10 +24,20 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# no need to copy around qml test files for shadow builds - just set the respective define # no need to copy around qml test files for shadow builds - just set the respective define
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
add_executable(TestStatusInputWithRegex main.cpp) add_executable(${PROJECT_NAME} main.cpp)
add_test(NAME TestStatusInputWithRegex COMMAND TestStatusInputWithRegex) add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})
target_link_libraries(TestStatusInputWithRegex PRIVATE # TODO: move this to a test helpers library
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
add_subdirectory(TestHelpers)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::QuickTest Qt${QT_VERSION_MAJOR}::QuickTest
Qt${QT_VERSION_MAJOR}::Qml) Qt${QT_VERSION_MAJOR}::Qml
Qt${QT_VERSION_MAJOR}::Quick
)

View File

@ -0,0 +1,14 @@
pragma Singleton
import QtQml 2.14
import QtTest 1.0
QtObject {
//> Simulate key and wait for side effects
function pressKeyAndWait(test, item, key) {
test.keyClick(key)
test.waitForRendering(item)
}
}

View File

@ -0,0 +1,3 @@
module StatusQ.TestHelpers
singleton TestUtils 0.1 TestUtils.qml

View File

@ -0,0 +1,12 @@
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.h
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)

View File

@ -0,0 +1,52 @@
#include "MonitorQtOutput.h"
#include <stdio.h>
#include <stdlib.h>
std::weak_ptr<QString> MonitorQtOutput::m_qtMessageOutputForSharing;
std::mutex MonitorQtOutput::m_mutex;
MonitorQtOutput::MonitorQtOutput()
{
// Ensure only one instance registers a handler
// Warning: don't QT's call loger functions inside the critical section
std::unique_lock<std::mutex> localLock(m_mutex);
auto globalMsgOut = m_qtMessageOutputForSharing.lock();
if(!globalMsgOut) {
// Install message handler if not already done
m_thisMessageOutput = std::make_shared<QString>();
m_qtMessageOutputForSharing = m_thisMessageOutput;
qInstallMessageHandler(qtMessageOutput);
}
else {
m_thisMessageOutput = globalMsgOut;
m_start = m_thisMessageOutput->length();
}
}
MonitorQtOutput::~MonitorQtOutput()
{
std::unique_lock<std::mutex> localLock(m_mutex);
if(m_thisMessageOutput.use_count() == 1) {
// Last instance, deregister the handler
qInstallMessageHandler(0);
m_thisMessageOutput.reset();
}
}
void
MonitorQtOutput::qtMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
std::unique_lock<std::mutex> localLock(m_mutex);
auto globalMsgOut = m_qtMessageOutputForSharing.lock();
assert(globalMsgOut != nullptr);
globalMsgOut->append(msg + '\n');
}
QString
MonitorQtOutput::qtOuput()
{
assert(m_thisMessageOutput->length() >= m_start);
return m_thisMessageOutput->right(m_thisMessageOutput->length() - m_start);
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <QQuickItem>
#include <QtGlobal>
#include <memory>
#include <mutex>
///
/// \brief Monitor output for tests and declarativelly control message handler availability
/// \todo Check that QML doesn't keep instance between test runs
///
class MonitorQtOutput : public QQuickItem
{
Q_OBJECT
public:
MonitorQtOutput();
~MonitorQtOutput();
Q_INVOKABLE QString qtOuput();
signals:
private:
static void qtMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
// Use it to keep track of qInstallMessageHandler call
static std::weak_ptr<QString> m_qtMessageOutputForSharing;
static std::mutex m_mutex;
std::shared_ptr<QString> m_thisMessageOutput;
int m_start = 0;
};

View File

@ -1,6 +1,8 @@
#include <QtQuickTest/quicktest.h> #include <QtQuickTest/quicktest.h>
#include <QQmlEngine> #include <QQmlEngine>
#include "TestHelpers/MonitorQtOutput.h"
class TestSetup : public QObject class TestSetup : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -13,7 +15,12 @@ public slots:
{ {
// TODO: Workaround until we make StatusQ a CMake library // TODO: Workaround until we make StatusQ a CMake library
engine->addImportPath("../../src/"); engine->addImportPath("../../src/");
engine->addImportPath(".");
// TODO: Alternative to not yet supported QML_ELEMENT
qmlRegisterType<MonitorQtOutput>("StatusQ.TestHelpers", 0, 1, "MonitorQtOutput");
} }
private:
MonitorQtOutput _monitorOutput;
}; };
QUICK_TEST_MAIN_WITH_SETUP(TestControls, TestSetup) QUICK_TEST_MAIN_WITH_SETUP(TestControls, TestSetup)

View File

@ -0,0 +1,52 @@
import QtQuick 2.0
import QtTest 1.0
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.TestHelpers 0.1
Item {
width: 300
height: 100
StatusBaseInput {
id: statusInput
text: "Control under test"
placeholderText: "Placeholder"
focus: true
}
TestCase {
id: testCase
name: "CheckQmlWarnings"
when: windowShown
//
// Test guards
function initTestCase() {
}
function cleanup() {
statusInput.text = ""
}
//
// Tests
function test_initial_empty_is_valid() {
mouseClick(statusInput)
// Do some editing
TestUtils.pressKeyAndWait(testCase, statusInput, Qt.Key_B)
TestUtils.pressKeyAndWait(testCase, statusInput, Qt.Key_Left)
TestUtils.pressKeyAndWait(testCase, statusInput, Qt.Key_A)
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
}
MonitorQtOutput {
id: qtOuput
}
}

View File

@ -4,6 +4,8 @@ import QtTest 1.0
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1
import StatusQ.TestHelpers 0.1
Item { Item {
width: 300 width: 300
height: 100 height: 100
@ -28,6 +30,8 @@ Item {
} }
TestCase { TestCase {
id: regexTC
name: "RegexValidationTest" name: "RegexValidationTest"
when: windowShown when: windowShown
@ -50,9 +54,9 @@ Item {
} }
function test_regex_validation() { function test_regex_validation() {
keyClick(Qt.Key_1) TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_1)
verify(statusInput.valid, "Expected valid input") verify(statusInput.valid, "Expected valid input")
keyClick(Qt.Key_Ampersand) TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Ampersand)
verify(!statusInput.valid, "Expected invalid input") verify(!statusInput.valid, "Expected invalid input")
} }
@ -61,10 +65,10 @@ Item {
verify(statusInput.valid, "Expected valid input") verify(statusInput.valid, "Expected valid input")
verify(statusInput.text.length === 0, "Expected no input") verify(statusInput.text.length === 0, "Expected no input")
keyClick(Qt.Key_2) TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_2)
verify(statusInput.valid, "Expected valid input") verify(statusInput.valid, "Expected valid input")
verify(statusInput.text === "2", "Expect one character") verify(statusInput.text === "2", "Expect one character")
keyClick(Qt.Key_Ampersand) TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Ampersand)
verify(statusInput.valid, "Expected invalid input") verify(statusInput.valid, "Expected invalid input")
verify(statusInput.text === "2", "Expect the same input") verify(statusInput.text === "2", "Expect the same input")
} }
@ -74,12 +78,43 @@ Item {
const appendInvalidChars = "#@!*" const appendInvalidChars = "#@!*"
statusInput.text = "invalid $" + appendInvalidChars statusInput.text = "invalid $" + appendInvalidChars
keyClick(Qt.Key_End) TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_End)
verify(!statusInput.valid, "Expected invalid input due to characters not matching") verify(!statusInput.valid, "Expected invalid input due to characters not matching")
// Delete invalid characters to get a valid text // Delete invalid characters to get a valid text
for(let i = 0; i < appendInvalidChars.length; ++i) for(let i = 0; i < appendInvalidChars.length; ++i)
keyClick(Qt.Key_Backspace) TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Backspace)
verify(statusInput.valid, "Expected valid input") verify(statusInput.valid, "Expected valid input")
} }
} }
TestCase {
id: qmlWarnTC
name: "CheckQmlWarnings"
when: windowShown
//
// Test guards
function initTestCase() {
}
function cleanup() {
statusInput.text = ""
statusInput.validationMode = _defaultValidationMode
}
//
// Tests
function test_initial_empty_is_valid() {
mouseClick(statusInput)
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
}
}
MonitorQtOutput {
id: qtOuput
}
} }

22
tests/readme.md Normal file
View File

@ -0,0 +1,22 @@
# Readme
## Developer instructions
CMake
```sh
cd StatusQ/tests/TestControls
cmake -B ./build/ -S .
cmake --build ./build/
./build/TestControls
```
QtCreator
- Open the StatusQ/tests/CMakeLists.txt
- Choose a QT kit to run the tests
- In the `Test Results` panel choose Run All Tests
## TODO
- [ ] Consolidate and integrate with https://github.com/status-im/desktop-ui-tests