Add appium test for opening the user details sheet and fix some accessibility problems
This commit is contained in:
@@ -45,3 +45,7 @@ License: BSD-2-Clause
|
|||||||
Files: autotests/data/*
|
Files: autotests/data/*
|
||||||
Copyright: none
|
Copyright: none
|
||||||
License: CC0-1.0
|
License: CC0-1.0
|
||||||
|
|
||||||
|
Files: appiumtests/data/*
|
||||||
|
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
License: CC0-1.0
|
||||||
|
|||||||
@@ -21,3 +21,8 @@ add_test(
|
|||||||
NAME logintest
|
NAME logintest
|
||||||
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/logintest.py
|
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/logintest.py
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME openuserdetailstest
|
||||||
|
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/openuserdetailstest.py
|
||||||
|
)
|
||||||
|
|||||||
3
appiumtests/data/sync_response_no_rooms.json
Normal file
3
appiumtests/data/sync_response_no_rooms.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"next_batch": "batch1234"
|
||||||
|
}
|
||||||
50
appiumtests/data/sync_response_rooms.json
Normal file
50
appiumtests/data/sync_response_rooms.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"next_batch": "batch1234",
|
||||||
|
"rooms": {
|
||||||
|
"join": {
|
||||||
|
"!room_id_1234:localhost:1234": {
|
||||||
|
"state": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"type": "m.room.member",
|
||||||
|
"state_key": "@user:localhost:1234",
|
||||||
|
"sender": "@user:localhost:1234",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"event_id": "$event_id_1234_0:localhost:1234",
|
||||||
|
"room_id": "!room_id_1234:localhost:1234",
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "",
|
||||||
|
"displayname": "A Display Name",
|
||||||
|
"membership": "join",
|
||||||
|
"reason": "Nothing"
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"type": "m.room.message",
|
||||||
|
"sender": "@user:localhost:1234",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"event_id": "$event_id_1234_1:localhost:1234",
|
||||||
|
"room_id": "!room_id_1234:localhost:1234",
|
||||||
|
"content": {
|
||||||
|
"body": "This is a message",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<a href=\"https://matrix.to/#/@user:localhost:1234\">User</a>:",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
|
||||||
|
import json
|
||||||
from flask import Flask, request, abort
|
from flask import Flask, request, abort
|
||||||
|
import os
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/_matrix/client/v3/login", methods=["GET"])
|
@app.route("/_matrix/client/v3/login", methods=["GET"])
|
||||||
def login_get():
|
def login_get():
|
||||||
result = dict()
|
result = dict()
|
||||||
@@ -12,6 +14,13 @@ def login_get():
|
|||||||
result["flows"][0]["type"] = "m.login.password"
|
result["flows"][0]["type"] = "m.login.password"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@app.route("/_matrix/client/v3/account/whoami", methods=["GET"])
|
||||||
|
def whoami():
|
||||||
|
result = dict()
|
||||||
|
result["device_id"] = "device_id_1234"
|
||||||
|
result["user_id"] = "@user:localhost:1234"
|
||||||
|
return result
|
||||||
|
|
||||||
@app.route("/_matrix/client/v3/login", methods=["POST"])
|
@app.route("/_matrix/client/v3/login", methods=["POST"])
|
||||||
def login_post():
|
def login_post():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
@@ -19,15 +28,22 @@ def login_post():
|
|||||||
abort(403)
|
abort(403)
|
||||||
print(data)
|
print(data)
|
||||||
result = dict()
|
result = dict()
|
||||||
result["access_token"] = "token_1234"
|
result["access_token"] = "token_login"
|
||||||
result["device_id"] = "device_1234"
|
result["device_id"] = "device_1234"
|
||||||
result["user_id"] = "@user:localhost:1234"
|
result["user_id"] = "@user:localhost:1234"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def load_json(name):
|
||||||
|
parts = __file__.split("/")
|
||||||
|
parts.pop()
|
||||||
|
datadir = "/".join(parts)
|
||||||
|
return json.loads(open(f"{datadir}/data/{name}.json").read())
|
||||||
|
|
||||||
|
|
||||||
@app.route("/_matrix/client/r0/sync")
|
@app.route("/_matrix/client/r0/sync")
|
||||||
def sync():
|
def sync():
|
||||||
result = dict()
|
|
||||||
result["next_batch"] = "batch1234"
|
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@app.route("/.well-known/matrix/client")
|
@app.route("/.well-known/matrix/client")
|
||||||
@@ -37,6 +53,18 @@ def well_known():
|
|||||||
reply["m.homeserver"]["base_url"] = "https://localhost:1234"
|
reply["m.homeserver"]["base_url"] = "https://localhost:1234"
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
@app.route("/_matrix/client/v3/profile/<id>")
|
||||||
|
def profile(id):
|
||||||
|
reply = dict()
|
||||||
|
reply["avatar_url"] = "mxc://localhost:1234/asdf1234"
|
||||||
|
reply["displayname"] = "User123"
|
||||||
|
return reply
|
||||||
|
|
||||||
|
@app.route("/_matrix/client/v3/keys/upload", methods=["POST"])
|
||||||
|
def upload_keys():
|
||||||
|
reply = dict()
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(ssl_context='adhoc', port=1234)
|
app.run(ssl_context='adhoc', port=1234)
|
||||||
|
|||||||
48
appiumtests/openuserdetailstest.py
Executable file
48
appiumtests/openuserdetailstest.py
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
|
||||||
|
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from appium import webdriver
|
||||||
|
from appium.options.common.base import AppiumOptions
|
||||||
|
from appium.webdriver.common.appiumby import AppiumBy
|
||||||
|
|
||||||
|
|
||||||
|
class OpenUserDetailsTest(unittest.TestCase):
|
||||||
|
|
||||||
|
mockServerProcess: subprocess.Popen
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
|
||||||
|
options = AppiumOptions()
|
||||||
|
options.set_capability("app", "neochat --ignore-ssl-errors --test")
|
||||||
|
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if not self._outcome.result.wasSuccessful():
|
||||||
|
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(self):
|
||||||
|
self.mockServerProcess.terminate()
|
||||||
|
self.driver.quit()
|
||||||
|
|
||||||
|
def test_open_sheet(self):
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="@user:localhost:1234").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Empty room (!room_id_1234:localhost:1234)").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="A Display Name").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Account Details")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -42,6 +42,8 @@
|
|||||||
#include "trayicon_sni.h"
|
#include "trayicon_sni.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool testMode = false;
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
Controller::Controller(QObject *parent)
|
Controller::Controller(QObject *parent)
|
||||||
@@ -56,9 +58,19 @@ Controller::Controller(QObject *parent)
|
|||||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this] {
|
if (!testMode) {
|
||||||
invokeLogin();
|
QTimer::singleShot(0, this, [this] {
|
||||||
});
|
invokeLogin();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
auto c = new NeoChatConnection(this);
|
||||||
|
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("token_1234"));
|
||||||
|
connect(c, &Connection::connected, this, [c, this]() {
|
||||||
|
m_accountRegistry.add(c);
|
||||||
|
c->syncLoop();
|
||||||
|
Q_EMIT initiated();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||||
delete m_trayIcon;
|
delete m_trayIcon;
|
||||||
@@ -434,3 +446,8 @@ AccountRegistry &Controller::accounts()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_controller.cpp"
|
#include "moc_controller.cpp"
|
||||||
|
|
||||||
|
void Controller::setTestMode(bool test)
|
||||||
|
{
|
||||||
|
testMode = test;
|
||||||
|
}
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ public:
|
|||||||
|
|
||||||
Quotient::AccountRegistry &accounts();
|
Quotient::AccountRegistry &accounts();
|
||||||
|
|
||||||
|
static void setTestMode(bool testMode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Controller(QObject *parent = nullptr);
|
explicit Controller(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,10 @@ int main(int argc, char *argv[])
|
|||||||
parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports matrix: url scheme"));
|
parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports matrix: url scheme"));
|
||||||
parser.addOption(QCommandLineOption("ignore-ssl-errors"_ls, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
|
parser.addOption(QCommandLineOption("ignore-ssl-errors"_ls, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
|
||||||
|
|
||||||
|
QCommandLineOption testOption("test"_ls, i18n("Only used for autotests"));
|
||||||
|
testOption.setFlags(QCommandLineOption::HiddenFromHelp);
|
||||||
|
parser.addOption(testOption);
|
||||||
|
|
||||||
#ifdef HAVE_KUNIFIEDPUSH
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
QCommandLineOption dbusActivatedOption(QStringLiteral("dbus-activated"), i18n("Internal usage only."));
|
QCommandLineOption dbusActivatedOption(QStringLiteral("dbus-activated"), i18n("Internal usage only."));
|
||||||
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
|
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
|
||||||
@@ -185,6 +189,7 @@ int main(int argc, char *argv[])
|
|||||||
about.setupCommandLine(&parser);
|
about.setupCommandLine(&parser);
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
about.processCommandLine(&parser);
|
about.processCommandLine(&parser);
|
||||||
|
Controller::setTestMode(parser.isSet("test"_ls));
|
||||||
|
|
||||||
#ifdef HAVE_KUNIFIEDPUSH
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
if (parser.isSet(dbusActivatedOption)) {
|
if (parser.isSet(dbusActivatedOption)) {
|
||||||
|
|||||||
@@ -139,20 +139,17 @@ QQC2.Control {
|
|||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.maximumWidth: root.maxContentWidth
|
Layout.maximumWidth: root.maxContentWidth
|
||||||
visible: root.showAuthor
|
visible: root.showAuthor
|
||||||
QQC2.Label {
|
QQC2.AbstractButton {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: root.author.displayName
|
contentItem: QQC2.Label {
|
||||||
color: root.author.color
|
text: root.author.displayName
|
||||||
textFormat: Text.PlainText
|
color: root.author.color
|
||||||
font.weight: Font.Bold
|
textFormat: Text.PlainText
|
||||||
elide: Text.ElideRight
|
font.weight: Font.Bold
|
||||||
|
elide: Text.ElideRight
|
||||||
TapHandler {
|
|
||||||
onTapped: RoomManager.visitUser(root.author.object, "mention")
|
|
||||||
}
|
|
||||||
HoverHandler {
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
}
|
}
|
||||||
|
Accessible.name: contentItem.text
|
||||||
|
onClicked: RoomManager.visitUser(root.author.object, "mention")
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
text: root.timeString
|
text: root.timeString
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import QtQuick.Layouts
|
|||||||
import Qt.labs.qmlmodels
|
import Qt.labs.qmlmodels
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
import org.kde.neochat.config
|
import org.kde.neochat.config
|
||||||
@@ -337,7 +337,7 @@ TimelineDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KirigamiComponents.Avatar {
|
KirigamiComponents.AvatarButton {
|
||||||
id: avatar
|
id: avatar
|
||||||
width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2: 0
|
width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2: 0
|
||||||
height: width
|
height: width
|
||||||
@@ -355,13 +355,7 @@ TimelineDelegate {
|
|||||||
source: root.author.avatarSource
|
source: root.author.avatarSource
|
||||||
color: root.author.color
|
color: root.author.color
|
||||||
|
|
||||||
MouseArea {
|
onClicked: RoomManager.visitUser(root.author.object, "mention")
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
RoomManager.visitUser(root.author.object, "mention")
|
|
||||||
}
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Bubble {
|
Bubble {
|
||||||
id: bubble
|
id: bubble
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Kirigami.Dialog {
|
|||||||
standardButtons: Kirigami.Dialog.NoButton
|
standardButtons: Kirigami.Dialog.NoButton
|
||||||
|
|
||||||
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||||
title: i18nc("@title:menu Account detail dialog", "Account detail")
|
title: i18nc("@title:menu Account details dialog", "Account Details")
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|||||||
Reference in New Issue
Block a user