Compare commits

...
Author SHA1 Message Date
Khushboo Mehta 2a1c11eb15 feat(StatusListItem): Add tags model under subtitle in a list item 2022-03-29 22:29:32 +02:00
Noelia a99e5e0201 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 19:23:46 +02:00
Stefan 8d780bb5b9 tests(StatusInput): add regression test to check for qml output
Also improves on the test structure
2022-03-29 19:23:46 +02:00
Michał Iskierko b0c00fd60d fix(StatusSearchPopup): Fix cutting text with big font (#604)
Decreasing padding.
Setting loading property default to false.

Fix #5197
2022-03-29 19:23:45 +02:00
Patryk Osmaczko 99659b2589 fix(StatusInput): add implicitHeight
It makes it work with layouts correctly.
2022-03-29 19:23:45 +02:00
Patryk Osmaczko ac3abff16f fix(StatusBaseInput): fix default padding when leftComponent is loaded 2022-03-29 19:23:45 +02:00
Alexandra Betouni cf0ba3f7a1 fix(StatusSeedPhraseInput): Increased suggestion match chars to 4 2022-03-29 19:23:45 +02:00
Alexandra Betouni 3c2e97c5e2 fix(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-26 00:29:04 +02:00
19 changed files with 339 additions and 35 deletions
+1
View File
@@ -16,3 +16,4 @@ sandbox/qmlcache_loader.cpp
doc/html
CMakeLists.txt.user
.vscode
build/
+19
View File
@@ -4,6 +4,7 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1
GridLayout {
columns: 1
@@ -338,6 +339,24 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
}
}
StatusListItem {
title: "List Item with Inline Tags"
icon.isLetterIdenticon: true
tagsModel : 2
tagsDelegate: StatusListItemTag {
color: "blue"
height: 24
radius: 6
closeButtonVisible: false
icon.emoji: "😁"
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: "helloworld.eth"
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
}
StatusListItem {
title: "List Item with Emoji"
subTitle: "Emoji"
@@ -48,7 +48,6 @@ Column {
popupMenuSlot.item.popup(mouse.x + 4, mouse.y + 6);
return
}
statusChatListCategory.opened = !opened;
}
}
onTitleClicked: statusChatListCategory.opened = !opened
@@ -12,6 +12,7 @@ Rectangle {
property alias additionalText: additionalText.text
property alias button: button
property alias expandableComponent: expandableRegion.sourceComponent
property alias expandableItem: expandableRegion.item
property int type: StatusExpandableItem.Type.Primary
property bool expandable: true
@@ -185,7 +186,7 @@ Rectangle {
anchors.leftMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 48 : 0
anchors.right: parent.right
anchors.rightMargin: (statusExpandableItem.type === StatusExpandableItem.Type.Primary) ? 16 : 0
active: false
visible: false
}
onExpandedChanged: {
@@ -202,13 +203,13 @@ Rectangle {
name: "EXPANDED"
PropertyChanges {target: expandImage; icon: "chevron-up"}
PropertyChanges {target: statusExpandableItem; height: 82 + expandableRegion.height + 22}
PropertyChanges {target: expandableRegion; active: true}
PropertyChanges {target: expandableRegion; visible: true}
},
State {
name: "COLLAPSED"
PropertyChanges {target: expandImage; icon: "chevron-down"}
PropertyChanges {target: statusExpandableItem; height: 82}
PropertyChanges {target: expandableRegion; active: false}
PropertyChanges {target: expandableRegion; visible: false}
}
]
+16 -1
View File
@@ -25,6 +25,8 @@ Rectangle {
property list<Item> components
property var bottomModel: []
property Component bottomDelegate
property var tagsModel: []
property Component tagsDelegate
property StatusIconSettings icon: StatusIconSettings {
height: isLetterIdenticon ? 40 : 20
@@ -71,6 +73,7 @@ Rectangle {
property alias statusListItemTertiaryTitle: statusListItemTertiaryTitle
property alias statusListItemComponentsSlot: statusListItemComponentsSlot
property alias statusListItemTagsSlot: statusListItemTagsSlot
property alias statusListItemInlineTagsSlot: statusListItemTagsSlotInline
signal clicked(string itemId)
signal titleClicked(string titleId)
@@ -247,7 +250,19 @@ Rectangle {
width: contentItem.width
implicitHeight: visible ? 22 : 0
}
Row {
id: statusListItemTagsSlotInline
anchors.top: statusListItemTertiaryTitle.bottom
width: parent.width
spacing: 10
clip: true
Repeater {
model: tagsModel
delegate: tagsDelegate
}
}
}
Row {
+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 {
@@ -17,6 +17,8 @@ Item {
property ListModel inputList: ListModel { }
property ListModel filteredList: ListModel { }
signal doneInsertingWord(string word)
signal keyPressed(var event)
signal editClicked()
onActiveFocusChanged: {
if (root.activeFocus) {
@@ -57,6 +59,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