Compare commits

...
Author SHA1 Message Date
Alexandra Betouni f9f054e3d6 feat(StatusSeedPhraseInput): exposed touch/keys pressed events
Exposed keys.onPressed signal, mouse on clicked signal
as well as tabNavItem to be set in KeyNavigation.tab
when needed

Needed for task https://github.com/status-im/status-desktop/issues/4955
2022-03-30 01:23:06 +03:00
Noelia 684f45cc08 fix(StatusChatListCategory): Selecting the category menu should not toggle the category itself
Closes `status-desktop` issue https://github.com/status-im/status-desktop/issues/4914
2022-03-29 11:50:29 +02:00
Stefan eae4e0f405 tests(StatusInput): add regression test to check for qml output
Also improves on the test structure
2022-03-28 22:52:36 +03:00
Michał Iskierko b2ac2794bb fix(StatusSearchPopup): Fix cutting text with big font (#604)
Decreasing padding.
Setting loading property default to false.

Fix #5197
2022-03-28 13:14:15 -04:00
Patryk Osmaczko b565a32c7e fix(StatusInput): add implicitHeight
It makes it work with layouts correctly.
2022-03-28 15:27:13 +02:00
Patryk Osmaczko a1835164d9 fix(StatusBaseInput): fix default padding when leftComponent is loaded 2022-03-28 15:26:39 +02:00
Alexandra Betouni 06e931d749 fix(StatusSeedPhraseInput): Increased suggestion match chars to 4 2022-03-26 07:46:11 +01:00
16 changed files with 341 additions and 32 deletions
+1
View File
@@ -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
+12 -3
View File
@@ -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
+24 -13
View File
@@ -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 {
+49 -1
View File
@@ -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 {
+3 -1
View File
@@ -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
+17 -7
View File
@@ -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;
};
+7
View File
@@ -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
}
}
@@ -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
}
}
+22
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