diff --git a/.reuse/dep5 b/.reuse/dep5 index cb155e003..2e7e8af2b 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -45,3 +45,7 @@ License: BSD-2-Clause Files: autotests/data/* Copyright: none License: CC0-1.0 + +Files: appiumtests/data/* +Copyright: 2023 Tobias Fella +License: CC0-1.0 diff --git a/appiumtests/CMakeLists.txt b/appiumtests/CMakeLists.txt index 61fe50fd4..75f8f84af 100644 --- a/appiumtests/CMakeLists.txt +++ b/appiumtests/CMakeLists.txt @@ -21,3 +21,8 @@ add_test( NAME logintest 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 +) diff --git a/appiumtests/data/sync_response_no_rooms.json b/appiumtests/data/sync_response_no_rooms.json new file mode 100644 index 000000000..73f36dc1c --- /dev/null +++ b/appiumtests/data/sync_response_no_rooms.json @@ -0,0 +1,3 @@ +{ + "next_batch": "batch1234" +} diff --git a/appiumtests/data/sync_response_rooms.json b/appiumtests/data/sync_response_rooms.json new file mode 100644 index 000000000..9ff2c5332 --- /dev/null +++ b/appiumtests/data/sync_response_rooms.json @@ -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": "User:", + "msgtype": "m.text" + }, + "unsigned": { + "age": 1234 + } + } + ] + } + } + } + } +} diff --git a/appiumtests/login-server.py b/appiumtests/login-server.py index f8c0a91de..b0ecfd485 100644 --- a/appiumtests/login-server.py +++ b/appiumtests/login-server.py @@ -1,10 +1,12 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2023 Tobias Fella - +import json from flask import Flask, request, abort +import os app = Flask(__name__) + @app.route("/_matrix/client/v3/login", methods=["GET"]) def login_get(): result = dict() @@ -12,6 +14,13 @@ def login_get(): result["flows"][0]["type"] = "m.login.password" 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"]) def login_post(): data = request.get_json() @@ -19,15 +28,22 @@ def login_post(): abort(403) print(data) result = dict() - result["access_token"] = "token_1234" + result["access_token"] = "token_login" result["device_id"] = "device_1234" result["user_id"] = "@user:localhost:1234" 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") 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 @app.route("/.well-known/matrix/client") @@ -37,6 +53,18 @@ def well_known(): reply["m.homeserver"]["base_url"] = "https://localhost:1234" return reply +@app.route("/_matrix/client/v3/profile/") +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__": app.run(ssl_context='adhoc', port=1234) diff --git a/appiumtests/openuserdetailstest.py b/appiumtests/openuserdetailstest.py new file mode 100755 index 000000000..a609c61c0 --- /dev/null +++ b/appiumtests/openuserdetailstest.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021-2022 Harald Sitter +# SPDX-FileCopyrightText: 2023 Tobias Fella + +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() diff --git a/src/controller.cpp b/src/controller.cpp index 644fd0ccb..181d187c5 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -42,6 +42,8 @@ #include "trayicon_sni.h" #endif +bool testMode = false; + using namespace Quotient; Controller::Controller(QObject *parent) @@ -56,9 +58,19 @@ Controller::Controller(QObject *parent) connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed); #endif - QTimer::singleShot(0, this, [this] { - invokeLogin(); - }); + if (!testMode) { + 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] { delete m_trayIcon; @@ -434,3 +446,8 @@ AccountRegistry &Controller::accounts() } #include "moc_controller.cpp" + +void Controller::setTestMode(bool test) +{ + testMode = test; +} diff --git a/src/controller.h b/src/controller.h index b222f4aac..61ea444bb 100644 --- a/src/controller.h +++ b/src/controller.h @@ -131,6 +131,8 @@ public: Quotient::AccountRegistry &accounts(); + static void setTestMode(bool testMode); + private: explicit Controller(QObject *parent = nullptr); diff --git a/src/main.cpp b/src/main.cpp index bc25a9ea6..bb26f8530 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -176,6 +176,10 @@ int main(int argc, char *argv[]) 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."))); + QCommandLineOption testOption("test"_ls, i18n("Only used for autotests")); + testOption.setFlags(QCommandLineOption::HiddenFromHelp); + parser.addOption(testOption); + #ifdef HAVE_KUNIFIEDPUSH QCommandLineOption dbusActivatedOption(QStringLiteral("dbus-activated"), i18n("Internal usage only.")); dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp); @@ -185,6 +189,7 @@ int main(int argc, char *argv[]) about.setupCommandLine(&parser); parser.process(app); about.processCommandLine(&parser); + Controller::setTestMode(parser.isSet("test"_ls)); #ifdef HAVE_KUNIFIEDPUSH if (parser.isSet(dbusActivatedOption)) { diff --git a/src/qml/Bubble.qml b/src/qml/Bubble.qml index 2281ee37c..27e0c088a 100644 --- a/src/qml/Bubble.qml +++ b/src/qml/Bubble.qml @@ -139,20 +139,17 @@ QQC2.Control { RowLayout { Layout.maximumWidth: root.maxContentWidth visible: root.showAuthor - QQC2.Label { + QQC2.AbstractButton { Layout.fillWidth: true - text: root.author.displayName - color: root.author.color - textFormat: Text.PlainText - font.weight: Font.Bold - elide: Text.ElideRight - - TapHandler { - onTapped: RoomManager.visitUser(root.author.object, "mention") - } - HoverHandler { - cursorShape: Qt.PointingHandCursor + contentItem: QQC2.Label { + text: root.author.displayName + color: root.author.color + textFormat: Text.PlainText + font.weight: Font.Bold + elide: Text.ElideRight } + Accessible.name: contentItem.text + onClicked: RoomManager.visitUser(root.author.object, "mention") } QQC2.Label { text: root.timeString diff --git a/src/qml/MessageDelegate.qml b/src/qml/MessageDelegate.qml index 742cd8013..d5bd07b5d 100644 --- a/src/qml/MessageDelegate.qml +++ b/src/qml/MessageDelegate.qml @@ -7,7 +7,7 @@ import QtQuick.Layouts import Qt.labs.qmlmodels 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.config @@ -337,7 +337,7 @@ TimelineDelegate { } } - KirigamiComponents.Avatar { + KirigamiComponents.AvatarButton { id: avatar width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2: 0 height: width @@ -355,13 +355,7 @@ TimelineDelegate { source: root.author.avatarSource color: root.author.color - MouseArea { - anchors.fill: parent - onClicked: { - RoomManager.visitUser(root.author.object, "mention") - } - cursorShape: Qt.PointingHandCursor - } + onClicked: RoomManager.visitUser(root.author.object, "mention") } Bubble { id: bubble diff --git a/src/qml/UserDetailDialog.qml b/src/qml/UserDetailDialog.qml index 745c325ea..cd4568fa5 100644 --- a/src/qml/UserDetailDialog.qml +++ b/src/qml/UserDetailDialog.qml @@ -29,7 +29,7 @@ Kirigami.Dialog { standardButtons: Kirigami.Dialog.NoButton 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 { spacing: 0