Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9f054e3d6 | ||
|
|
684f45cc08 | ||
|
|
eae4e0f405 | ||
|
|
b2ac2794bb | ||
|
|
b565a32c7e | ||
|
|
a1835164d9 | ||
|
|
06e931d749 |
@@ -16,3 +16,4 @@ sandbox/qmlcache_loader.cpp
|
||||
doc/html
|
||||
CMakeLists.txt.user
|
||||
.vscode
|
||||
build/
|
||||
|
||||
@@ -48,7 +48,6 @@ Column {
|
||||
popupMenuSlot.item.popup(mouse.x + 4, mouse.y + 6);
|
||||
return
|
||||
}
|
||||
statusChatListCategory.opened = !opened;
|
||||
}
|
||||
}
|
||||
onTitleClicked: statusChatListCategory.opened = !opened
|
||||
|
||||
@@ -37,10 +37,11 @@ Item {
|
||||
property alias placeholderTextColor: placeholder.color
|
||||
property alias placeholderFont: placeholder.font
|
||||
|
||||
property real leftPadding: 16
|
||||
property real leftPadding: leftComponentLoader.item ? 8 : 16
|
||||
property real rightPadding: 16
|
||||
property real topPadding: 12
|
||||
property real bottomPadding: 12
|
||||
property var tabNavItem: null
|
||||
|
||||
property real minimumHeight: 0
|
||||
property real maximumHeight: 0
|
||||
@@ -72,6 +73,8 @@ Item {
|
||||
property Component rightComponent
|
||||
|
||||
signal iconClicked
|
||||
signal keyPressed(var event)
|
||||
signal editClicked()
|
||||
|
||||
implicitWidth: 448
|
||||
implicitHeight: multiline ? Math.min(Math.max(
|
||||
@@ -115,8 +118,8 @@ Item {
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: {
|
||||
edit.forceActiveFocus()
|
||||
root.editClicked()
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
fill: parent
|
||||
@@ -127,6 +130,7 @@ Item {
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
id: leftComponentLoader
|
||||
sourceComponent: {
|
||||
if (root.leftComponent) return root.leftComponent
|
||||
if (!root.leftIcon) return undefined
|
||||
@@ -185,7 +189,12 @@ Item {
|
||||
Keys.onReturnPressed: event.accepted = !multiline
|
||||
Keys.onEnterPressed: event.accepted = !multiline
|
||||
Keys.forwardTo: [root]
|
||||
Keys.onPressed: edit.keyEvent = event.key
|
||||
KeyNavigation.priority: !!root.tabNavItem ? KeyNavigation.BeforeItem : KeyNavigation.AfterItem
|
||||
KeyNavigation.tab: root.tabNavItem
|
||||
Keys.onPressed: {
|
||||
edit.keyEvent = event.key
|
||||
root.keyPressed(event);
|
||||
}
|
||||
|
||||
onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
|
||||
onActiveFocusChanged: if (root.pristine) root.pristine = false
|
||||
|
||||
@@ -33,19 +33,22 @@ import StatusQ.Controls.Validators 0.1
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: 480
|
||||
height: (label.visible ?
|
||||
label.anchors.topMargin +
|
||||
label.height :
|
||||
charLimitLabel.visible ?
|
||||
charLimitLabel.anchors.topMargin +
|
||||
charLimitLabel.height :
|
||||
0) +
|
||||
statusBaseInput.anchors.topMargin +
|
||||
statusBaseInput.height +
|
||||
(errorMessage.visible ?
|
||||
errorMessage.anchors.topMargin +
|
||||
errorMessage.height :
|
||||
0) + 8
|
||||
implicitHeight: (label.visible ?
|
||||
label.anchors.topMargin +
|
||||
label.height :
|
||||
charLimitLabel.visible ?
|
||||
charLimitLabel.anchors.topMargin +
|
||||
charLimitLabel.height :
|
||||
0) +
|
||||
statusBaseInput.anchors.topMargin +
|
||||
statusBaseInput.height +
|
||||
(errorMessage.visible ?
|
||||
errorMessage.anchors.topMargin +
|
||||
errorMessage.height :
|
||||
0) + 8
|
||||
|
||||
height: implicitHeight
|
||||
width: implicitWidth
|
||||
|
||||
property alias input: statusBaseInput
|
||||
property alias valid: statusBaseInput.valid
|
||||
@@ -104,6 +107,8 @@ Item {
|
||||
property var pendingValidators: []
|
||||
|
||||
signal iconClicked()
|
||||
signal keyPressed(var event)
|
||||
signal editClicked()
|
||||
|
||||
/*!
|
||||
\qmltype ValidationMode
|
||||
@@ -287,6 +292,12 @@ Item {
|
||||
|
||||
Keys.forwardTo: [root]
|
||||
onIconClicked: root.iconClicked()
|
||||
onKeyPressed: {
|
||||
root.keyPressed(event);
|
||||
}
|
||||
onEditChanged: {
|
||||
root.editClicked();
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
|
||||
@@ -7,6 +7,42 @@ import StatusQ.Popups 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusSeedPhraseInput
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Controls
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief Displays a text input with suggestions. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
The \c StatusSeedPhraseInput item displays a text input with suggestions filtered based on the typed text.
|
||||
For example:
|
||||
|
||||
\qml
|
||||
StatusSeedPhraseInput {
|
||||
id: statusSeedInput
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
textEdit.input.anchors.leftMargin: 16
|
||||
textEdit.input.anchors.rightMargin: 16
|
||||
textEdit.input.anchors.topMargin: 11
|
||||
leftComponentText: "1"
|
||||
inputList: ListModel {
|
||||
ListElement {
|
||||
seedWord: "panda"
|
||||
}
|
||||
ListElement {
|
||||
seedWord: "posible"
|
||||
}
|
||||
ListElement {
|
||||
seedWord: "wing"
|
||||
}
|
||||
}
|
||||
}
|
||||
\endqml
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 162
|
||||
@@ -17,6 +53,12 @@ Item {
|
||||
property ListModel inputList: ListModel { }
|
||||
property ListModel filteredList: ListModel { }
|
||||
signal doneInsertingWord(string word)
|
||||
/*
|
||||
This signal is emitted when the user pressed a keyboard key and passes as a
|
||||
parameter the event. The corresponding handler is \c onKeyPressed.
|
||||
*/
|
||||
signal keyPressed(var event)
|
||||
signal editClicked()
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (root.activeFocus) {
|
||||
@@ -46,7 +88,7 @@ Item {
|
||||
}
|
||||
}
|
||||
seedSuggestionsList.model = filteredList;
|
||||
if ((text.length === 3) && (filteredList.count === 1) &&
|
||||
if ((text.length === 4) && (filteredList.count === 1) &&
|
||||
((input.edit.keyEvent !== Qt.Key_Backspace) && (input.edit.keyEvent !== Qt.Key_Delete))) {
|
||||
seedWordInput.text = filteredList.get(0).seedWord;
|
||||
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length;
|
||||
@@ -57,6 +99,12 @@ Item {
|
||||
seedSuggestionsList.model = 0;
|
||||
}
|
||||
}
|
||||
onKeyPressed: {
|
||||
root.keyPressed(event);
|
||||
}
|
||||
onEditClicked: {
|
||||
root.editClicked();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
||||
@@ -20,7 +20,7 @@ StatusModal {
|
||||
property string searchText: contentItem.searchText
|
||||
property string noResultsLabel: "No results"
|
||||
property string defaultSearchLocationText: "Anywhere"
|
||||
property bool loading
|
||||
property bool loading: false
|
||||
property Menu searchOptionsPopupMenu: Menu { }
|
||||
property var searchResults: [ ]
|
||||
property var searchSelectionButton
|
||||
@@ -95,6 +95,8 @@ StatusModal {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
focus: true
|
||||
font.pixelSize: 28
|
||||
topPadding: 5 //smaller padding to handle bigger font
|
||||
bottomPadding: 5
|
||||
clearable: true
|
||||
showBackground: false
|
||||
font.family: Theme.palette.baseFont.name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(TestStatusInputWithRegex LANGUAGES CXX)
|
||||
project(TestControls LANGUAGES CXX)
|
||||
|
||||
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(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${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml REQUIRED)
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS QuickTest Qml Quick REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml Quick REQUIRED)
|
||||
|
||||
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
|
||||
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
add_executable(TestStatusInputWithRegex main.cpp)
|
||||
add_test(NAME TestStatusInputWithRegex COMMAND TestStatusInputWithRegex)
|
||||
add_executable(${PROJECT_NAME} main.cpp)
|
||||
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}::Qml)
|
||||
Qt${QT_VERSION_MAJOR}::Qml
|
||||
Qt${QT_VERSION_MAJOR}::Quick
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module StatusQ.TestHelpers
|
||||
|
||||
singleton TestUtils 0.1 TestUtils.qml
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <QtQuickTest/quicktest.h>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "TestHelpers/MonitorQtOutput.h"
|
||||
|
||||
class TestSetup : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -13,7 +15,12 @@ public slots:
|
||||
{
|
||||
// TODO: Workaround until we make StatusQ a CMake library
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+41
-6
@@ -4,6 +4,8 @@ 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
|
||||
@@ -28,6 +30,8 @@ Item {
|
||||
}
|
||||
|
||||
TestCase {
|
||||
id: regexTC
|
||||
|
||||
name: "RegexValidationTest"
|
||||
|
||||
when: windowShown
|
||||
@@ -50,9 +54,9 @@ Item {
|
||||
}
|
||||
|
||||
function test_regex_validation() {
|
||||
keyClick(Qt.Key_1)
|
||||
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_1)
|
||||
verify(statusInput.valid, "Expected valid input")
|
||||
keyClick(Qt.Key_Ampersand)
|
||||
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Ampersand)
|
||||
verify(!statusInput.valid, "Expected invalid input")
|
||||
}
|
||||
|
||||
@@ -61,10 +65,10 @@ Item {
|
||||
|
||||
verify(statusInput.valid, "Expected valid 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.text === "2", "Expect one character")
|
||||
keyClick(Qt.Key_Ampersand)
|
||||
TestUtils.pressKeyAndWait(regexTC, statusInput, Qt.Key_Ampersand)
|
||||
verify(statusInput.valid, "Expected invalid input")
|
||||
verify(statusInput.text === "2", "Expect the same input")
|
||||
}
|
||||
@@ -74,12 +78,43 @@ Item {
|
||||
const 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")
|
||||
// Delete invalid characters to get a valid text
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user