Compare commits
2 Commits
master
...
work/test-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf754711e3 | ||
|
|
32d0f846ee |
@@ -1,2 +0,0 @@
|
||||
[General]
|
||||
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp,i18n"
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.10",
|
||||
"runtime-version": "6.9",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
@@ -20,32 +20,21 @@
|
||||
"--talk-name=org.kde.kwalletd5",
|
||||
"--talk-name=org.kde.StatusNotifierWatcher",
|
||||
"--talk-name=org.freedesktop.secrets",
|
||||
"--talk-name=org.kde.kuiserver",
|
||||
"--own-name=org.kde.StatusNotifierItem-2-2"
|
||||
],
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/lib/*.a",
|
||||
"/lib/cmake",
|
||||
"/lib/pkgconfig",
|
||||
"/share/ndk-modules"
|
||||
],
|
||||
"modules": [
|
||||
{
|
||||
"name": "opencv",
|
||||
"name": "kirigamiaddons",
|
||||
"config-opts": [
|
||||
"-DBUILD_TESTS=OFF",
|
||||
"-DWITH_GTK=OFF",
|
||||
"-DBUILD_LIST=core,imgproc"
|
||||
"-DBUILD_TESTING=OFF"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/opencv/opencv"
|
||||
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
|
||||
}
|
||||
],
|
||||
"builddir": true
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "kquickimageeditor",
|
||||
@@ -65,7 +54,6 @@
|
||||
"name": "olm",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [
|
||||
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"-DOLM_TESTS=OFF"
|
||||
],
|
||||
"sources": [
|
||||
@@ -94,8 +82,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.7.tar.xz",
|
||||
"sha256": "6b452e4750590a2b5617adc40026f28d2f4903de15f1250e1d1c40bfd68ed55e",
|
||||
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz",
|
||||
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090",
|
||||
"x-checker-data": {
|
||||
"type": "gnome",
|
||||
"name": "libsecret",
|
||||
@@ -165,20 +153,16 @@
|
||||
"name": "kunifiedpush",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-DENABLE_TESTING=OFF",
|
||||
"-DKUNIFIEDPUSH_CLIENT_ONLY=ON"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.kde.org/stable/release-service/25.08.3/src/kunifiedpush-25.08.3.tar.xz",
|
||||
"sha256": "e8c924438d5359f0fa0930ab35111012076e3a0ff4e959d6929595571383320a",
|
||||
"url": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-1.0.0.tar.xz",
|
||||
"sha256": "2ddeba21306d0307114ec50a2c38159ec62359f9fc6cdd58da30a369fbd550cf",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 8763,
|
||||
"project-id": 375055,
|
||||
"stable-only": true,
|
||||
"url-template": "https://download.kde.org/stable/release-service/$version/src/kunifiedpush-$version.tar.xz"
|
||||
"url-template": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-$version.tar.xz"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,19 +3,20 @@
|
||||
|
||||
include:
|
||||
- project: sysadmin/ci-utilities
|
||||
ref: work/switch-vm-ci
|
||||
file:
|
||||
- /gitlab-templates/reuse-lint.yml
|
||||
- /gitlab-templates/json-validation.yml
|
||||
- /gitlab-templates/xml-lint.yml
|
||||
- /gitlab-templates/yaml-lint.yml
|
||||
- /gitlab-templates/android-qt6.yml
|
||||
#- /gitlab-templates/reuse-lint.yml
|
||||
#- /gitlab-templates/json-validation.yml
|
||||
#- /gitlab-templates/xml-lint.yml
|
||||
#- /gitlab-templates/yaml-lint.yml
|
||||
#- /gitlab-templates/android-qt6.yml
|
||||
- /gitlab-templates/linux-qt6.yml
|
||||
- /gitlab-templates/linux-qt6-next.yml
|
||||
- /gitlab-templates/windows-qt6.yml
|
||||
# - /gitlab-templates/freebsd-qt6.yml
|
||||
#- /gitlab-templates/windows-qt6.yml
|
||||
#- /gitlab-templates/freebsd-qt6.yml
|
||||
- /gitlab-templates/flatpak.yml
|
||||
- /gitlab-templates/snap-snapcraft-lxd.yml
|
||||
- /gitlab-templates/craft-android-qt6-apks.yml
|
||||
- /gitlab-templates/craft-appimage-qt6.yml
|
||||
#- /gitlab-templates/craft-android-qt6-apks.yml
|
||||
#- /gitlab-templates/craft-appimage-qt6.yml
|
||||
- /gitlab-templates/craft-windows-x86-64-qt6.yml
|
||||
- /gitlab-templates/craft-windows-appx-qt6.yml
|
||||
|
||||
@@ -10,11 +10,11 @@ Dependencies:
|
||||
'frameworks/ki18n': '@latest-kf6'
|
||||
'frameworks/kconfig': '@latest-kf6'
|
||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||
'frameworks/kiconthemes': '@latest-kf6'
|
||||
'frameworks/kitemmodels': '@latest-kf6'
|
||||
'frameworks/kquickcharts': '@latest-kf6'
|
||||
'frameworks/knotifications': '@latest-kf6'
|
||||
'frameworks/kcolorscheme': '@latest-kf6'
|
||||
'frameworks/kiconthemes': '@latest-kf6'
|
||||
'libraries/kquickimageeditor': '@latest-kf6'
|
||||
'frameworks/sonnet': '@latest-kf6'
|
||||
'frameworks/prison': '@latest-kf6'
|
||||
@@ -28,6 +28,8 @@ Dependencies:
|
||||
'frameworks/qqc2-desktop-style': '@latest-kf6'
|
||||
'frameworks/kio': '@latest-kf6'
|
||||
'frameworks/kwindowsystem': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
'frameworks/kcrash': '@latest-kf6'
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
@@ -41,5 +43,3 @@ Dependencies:
|
||||
Options:
|
||||
per-test-timeout: 90
|
||||
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']
|
||||
run-qmllint: True
|
||||
enable-lsan: True
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "26")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "25")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.17")
|
||||
set(QT_MIN_VERSION "6.9")
|
||||
set(KF_MIN_VERSION "6.12")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
@@ -24,7 +24,7 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(KDE_COMPILERSETTINGS_LEVEL 6.17)
|
||||
set(KDE_COMPILERSETTINGS_LEVEL 6.0)
|
||||
|
||||
include(FeatureSummary)
|
||||
include(ECMSetupVersion)
|
||||
@@ -39,16 +39,17 @@ include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
include(ECMQmlModule)
|
||||
include(ECMDeprecationSettings)
|
||||
include(GenerateExportHeader)
|
||||
include(ECMGenerateHeaders)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
|
||||
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
|
||||
if(NEOCHAT_FLATPAK)
|
||||
include(cmake/Flatpak.cmake)
|
||||
endif()
|
||||
|
||||
ecm_set_disabled_deprecation_versions(Qt 6.9.0 KF 6.17.0)
|
||||
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
|
||||
|
||||
ecm_setup_version(${PROJECT_VERSION}
|
||||
VARIABLE_PREFIX NEOCHAT
|
||||
@@ -65,7 +66,7 @@ if (QT_KNOWN_POLICY_QTP0004)
|
||||
qt_policy(SET QTP0004 NEW)
|
||||
endif ()
|
||||
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme IconThemes)
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme)
|
||||
set_package_properties(KF6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
@@ -74,7 +75,7 @@ set_package_properties(KF6Kirigami PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
find_package(KF6KirigamiAddons 1.10.0 REQUIRED)
|
||||
find_package(KF6KirigamiAddons 1.6.0 REQUIRED)
|
||||
|
||||
if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Purpose)
|
||||
@@ -88,7 +89,7 @@ if(ANDROID)
|
||||
)
|
||||
else()
|
||||
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem Crash)
|
||||
find_package(KF6SyntaxHighlighting ${KF_MIN_VERSION} REQUIRED)
|
||||
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
@@ -106,7 +107,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(QuotientQt6 0.9.5)
|
||||
find_package(QuotientQt6 0.9.1)
|
||||
set_package_properties(QuotientQt6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Qt wrapper around Matrix API"
|
||||
@@ -178,7 +179,7 @@ add_definitions(-DQT_NO_FOREACH)
|
||||
add_subdirectory(src)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer QuickTest)
|
||||
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer)
|
||||
add_subdirectory(autotests)
|
||||
# add_subdirectory(appiumtests)
|
||||
if (NOT ANDROID)
|
||||
|
||||
19
README.md
19
README.md
@@ -25,10 +25,15 @@ Qt-based SDK for the [Matrix Protocol](https://spec.matrix.org/).
|
||||
|
||||
## Features
|
||||
|
||||
NeoChat aims to be a fully featured application for the Matrix specification. As such, most parts of the current specification are supported, with the notable exceptions
|
||||
of VoIP, threads, and some aspects of End-to-End Encryption. There are a few other smaller omissions due to the Matrix spec constantly
|
||||
NeoChat aims to be a fully featured application for the Matrix specification. As such most parts of the current specification are supported, with the notable exceptions
|
||||
of VoIP, threads, and some aspects of End-to-End Encryption. There are a few other smaller omissions due to the fact that the Matrix spec is constantly
|
||||
evolving, but the aim remains to provide eventual support for the entire spec.
|
||||
|
||||
Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:
|
||||
- Polls - MSC3381
|
||||
- Sticker Packs - MSC2545
|
||||
- Location Events - MSC3488
|
||||
|
||||
## Get it
|
||||
|
||||
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
|
||||
@@ -43,12 +48,12 @@ The best way to build KDE apps during development is to use `kdesrc-build`. The
|
||||
the KDE community website's get involved section under [development](https://community.kde.org/Get_Involved/development). This
|
||||
is primarily aimed at Linux development.
|
||||
|
||||
For Windows and Android, [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
|
||||
For Windows and Android [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
|
||||
development environments for [Windows](https://community.kde.org/Get_Involved/development/Windows) and [Android](https://develop.kde.org/docs/packaging/android/building_applications/).
|
||||
|
||||
## Running
|
||||
|
||||
Start the executable in your preferred way – either from the build directory or from the installed location.
|
||||
Just start the executable in your preferred way - either from the build directory or from the installed location.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -61,12 +66,12 @@ be complete.
|
||||
|
||||

|
||||
|
||||
Currently, the number of tests is limited but growing. If anyone wants to help improve this, those
|
||||
Currently the number of tests is limited, but growing. If anyone wants to help improve this, those
|
||||
contributions would be especially welcome.
|
||||
|
||||
## Contributing
|
||||
|
||||
As is the case throughout the KDE ecosystem, contributions are welcome from all. The code base is managed in the
|
||||
As is the case throughout the KDE ecosystem contributions are welcome from all. The code base is managed in the
|
||||
[NeoChat repository](https://invent.kde.org/network/neochat) of the KDE Gitlab instance.
|
||||
|
||||
- [Code of Conduct](https://kde.org/code-of-conduct)
|
||||
@@ -81,7 +86,7 @@ The best place to reach the maintainers is on the KDE Matrix instance in the Neo
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
NeoChat uses [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
|
||||
NeoChat utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
|
||||
|
||||
NeoChat is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).
|
||||
|
||||
|
||||
@@ -88,9 +88,3 @@ path = "memorytests/memtest-sync.json"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2024 James Graham <james.h.graham@protonmail.com>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
[[annotations]]
|
||||
path = ".contextProperties.ini"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
@@ -11,7 +11,7 @@ add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
|
||||
|
||||
ecm_add_test(
|
||||
neochatroomtest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test Qt::HttpServer neochat_server
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME neochatroomtest
|
||||
)
|
||||
|
||||
@@ -41,10 +41,16 @@ ecm_add_test(
|
||||
|
||||
ecm_add_test(
|
||||
chatbarcachetest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test Qt::HttpServer neochat_server
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatbarcachetest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
chatdocumenthandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatdocumenthandlertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
timelinemessagemodeltest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
@@ -86,63 +92,3 @@ ecm_add_test(
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||
TEST_NAME actionstest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
servernoticestest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||
TEST_NAME servernoticestest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
roommanagertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||
TEST_NAME roommanagertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
modeltest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server Devtools
|
||||
TEST_NAME modeltest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
blockcachetest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME blockcachetest
|
||||
)
|
||||
|
||||
macro(add_qml_tests)
|
||||
if (WIN32)
|
||||
set(_extra_args -platform offscreen)
|
||||
endif()
|
||||
|
||||
foreach(test ${ARGV})
|
||||
add_test(NAME ${test}
|
||||
COMMAND qmltest
|
||||
${_extra_args}
|
||||
-input ${test}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
add_executable(qmltest qmltest.cpp
|
||||
chatkeyhelpertesthelper.h
|
||||
chatmarkdownhelpertestwrapper.h
|
||||
chattextitemhelpertesthelper.h
|
||||
)
|
||||
qt_add_qml_module(qmltest URI NeoChatTestUtils)
|
||||
|
||||
target_link_libraries(qmltest
|
||||
PRIVATE
|
||||
Qt6::Qml
|
||||
Qt6::QuickTest
|
||||
LibNeoChat
|
||||
LibNeoChatplugin
|
||||
)
|
||||
|
||||
add_qml_tests(
|
||||
chattextitemhelpertest.qml
|
||||
chatmarkdownhelpertest.qml
|
||||
chatkeyhelpertest.qml
|
||||
)
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
#include <QVariantList>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "blockcache.h"
|
||||
#include "chatbarcache.h"
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "models/actionsmodel.h"
|
||||
|
||||
#include "server.h"
|
||||
@@ -65,7 +63,7 @@ void ActionsTest::testActions_data()
|
||||
QTest::addColumn<std::optional<QString>>("resultText");
|
||||
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
|
||||
|
||||
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\\\_(ツ)\\_/¯ Hello"_s)
|
||||
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\_(ツ)_/¯ Hello"_s)
|
||||
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)
|
||||
@@ -90,8 +88,8 @@ void ActionsTest::testActions()
|
||||
QFETCH(std::optional<QString>, resultText);
|
||||
QFETCH(std::optional<Quotient::RoomMessageEvent::MsgType>, type);
|
||||
|
||||
auto cache = new ChatBarCache(room);
|
||||
cache->cache() += Block::CacheItem{.type = MessageComponentType::Text, .content = QTextDocumentFragment::fromMarkdown(command)};
|
||||
auto cache = new ChatBarCache();
|
||||
cache->setText(command);
|
||||
auto result = ActionsModel::handleAction(room, cache);
|
||||
QCOMPARE(resultText, std::get<std::optional<QString>>(result));
|
||||
QCOMPARE(type, std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result));
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include "blockcache.h"
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
|
||||
using namespace Block;
|
||||
|
||||
class BlockCacheTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void toStringTest_data();
|
||||
void toStringTest();
|
||||
};
|
||||
|
||||
void BlockCacheTest::toStringTest_data()
|
||||
{
|
||||
QTest::addColumn<QString>("inputString");
|
||||
QTest::addColumn<MessageComponentType::Type>("itemType");
|
||||
QTest::addColumn<QString>("outputstring");
|
||||
|
||||
QTest::newRow("plainText") << u"test string"_s << MessageComponentType::Text << u"test string"_s;
|
||||
QTest::newRow("list") << u"- list 1\n- list 2\n- list 3\n"_s << MessageComponentType::Text << u"- list 1\n- list 2\n- list 3"_s;
|
||||
QTest::newRow("code") << u"for (some code) {\n\n do something\n\n}"_s << MessageComponentType::Code
|
||||
<< u"```\nfor (some code) {\n do something\n}\n```"_s;
|
||||
QTest::newRow("quote") << u"\"this is a quote\""_s << MessageComponentType::Quote << u"> this is a quote"_s;
|
||||
QTest::newRow("heading") << u"# heading\n\nnext line"_s << MessageComponentType::Text << u"# heading\n\nnext line"_s;
|
||||
}
|
||||
|
||||
void BlockCacheTest::toStringTest()
|
||||
{
|
||||
QFETCH(QString, inputString);
|
||||
QFETCH(MessageComponentType::Type, itemType);
|
||||
QFETCH(QString, outputstring);
|
||||
|
||||
Cache cache;
|
||||
cache += CacheItem{
|
||||
.type = itemType,
|
||||
.content = QTextDocumentFragment::fromMarkdown(inputString),
|
||||
};
|
||||
|
||||
QCOMPARE(cache.toString(), outputstring);
|
||||
}
|
||||
|
||||
QTEST_MAIN(BlockCacheTest)
|
||||
#include "blockcachetest.moc"
|
||||
@@ -6,19 +6,13 @@
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include <QSignalSpy>
|
||||
#include <Quotient/roommember.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
#include <qtestcase.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "blockcache.h"
|
||||
#include "chatbarcache.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include "server.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -29,54 +23,30 @@ class ChatBarCacheTest : public QObject
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
Server server;
|
||||
QString eventId;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void empty();
|
||||
void noRoom();
|
||||
void badParent();
|
||||
void reply();
|
||||
void replyMissingUser();
|
||||
void edit();
|
||||
void attachment();
|
||||
};
|
||||
|
||||
void ChatBarCacheTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true, this);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
|
||||
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
eventId = server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
|
||||
server.joinUser(room->id(), u"@foo:server.com"_s);
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, "test-min-sync.json"_L1);
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::empty()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
|
||||
QCOMPARE(chatBarCache->cache().toString(), QString());
|
||||
QCOMPARE(chatBarCache->text(), QString());
|
||||
QCOMPARE(chatBarCache->isReplying(), false);
|
||||
QCOMPARE(chatBarCache->replyId(), QString());
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
@@ -86,84 +56,84 @@ void ChatBarCacheTest::empty()
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::noRoom()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache());
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
// These should return empty even though a reply ID has been set because the
|
||||
// ChatBarCache has no parent.
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationAuthor(), Quotient::RoomMember());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::badParent()
|
||||
{
|
||||
QScopedPointer<QObject> badParent(new QObject());
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get()));
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
// These should return empty even though a reply ID has been set because the
|
||||
// ChatBarCache has no parent.
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationAuthor(), Quotient::RoomMember());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::reply()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
chatBarCache->cache() += Block::CacheItem{.type = MessageComponentType::Text, .content = QTextDocumentFragment::fromMarkdown(u"some text"_s)};
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
chatBarCache->setReplyId(eventId);
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->cache().toString(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), true);
|
||||
QCOMPARE(chatBarCache->replyId(), eventId);
|
||||
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::replyMissingUser()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
chatBarCache->cache() += Block::CacheItem{.type = MessageComponentType::Text, .content = QTextDocumentFragment::fromMarkdown(u"some text"_s)};
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
chatBarCache->setReplyId(eventId);
|
||||
|
||||
QCOMPARE(chatBarCache->cache().toString(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), true);
|
||||
QCOMPARE(chatBarCache->replyId(), eventId);
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
||||
|
||||
QSignalSpy relationAuthorIsPresentSpy(chatBarCache.get(), &ChatBarCache::relationAuthorIsPresentChanged);
|
||||
|
||||
// sync again, which will simulate the reply user leaving the room
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
server.sendStateEvent(room->id(), u"m.room.member"_s, u"@foo:server.com"_s, {{u"membership"_s, u"leave"_s}});
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
|
||||
QTRY_COMPARE(relationAuthorIsPresentSpy.count(), 1);
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), false);
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::edit()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
|
||||
chatBarCache->cache() += Block::CacheItem{.type = MessageComponentType::Text, .content = QTextDocumentFragment::fromMarkdown(u"some text"_s)};
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
|
||||
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [](const QString &oldEventId, const QString &newEventId) {
|
||||
QCOMPARE(oldEventId, QString());
|
||||
QCOMPARE(newEventId, eventId);
|
||||
QCOMPARE(newEventId, QString(u"$153456789:example.org"_s));
|
||||
});
|
||||
chatBarCache->setEditId(eventId);
|
||||
chatBarCache->setEditId(u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->cache().toString(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), false);
|
||||
QCOMPARE(chatBarCache->replyId(), QString());
|
||||
QCOMPARE(chatBarCache->isEditing(), true);
|
||||
QCOMPARE(chatBarCache->editId(), eventId);
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
|
||||
QCOMPARE(chatBarCache->editId(), u"$153456789:example.org"_s);
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::attachment()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
chatBarCache->cache() += Block::CacheItem{.type = MessageComponentType::Text, .content = QTextDocumentFragment::fromMarkdown(u"some text"_s)};
|
||||
chatBarCache->setEditId(eventId);
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setEditId(u"$153456789:example.org"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->cache().toString(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), false);
|
||||
QCOMPARE(chatBarCache->replyId(), QString());
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
|
||||
36
autotests/chatdocumenthandlertest.cpp
Normal file
36
autotests/chatdocumenthandlertest.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
class ChatDocumentHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
ChatDocumentHandler emptyHandler;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void nullComplete();
|
||||
};
|
||||
|
||||
void ChatDocumentHandlerTest::initTestCase()
|
||||
{
|
||||
// HACK: this is to stop KStatusNotifierItem SEGFAULTING on cleanup.
|
||||
NeoChatConfig::self()->setSystemTray(false);
|
||||
}
|
||||
|
||||
void ChatDocumentHandlerTest::nullComplete()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "complete called with m_document set to nullptr.");
|
||||
emptyHandler.complete(0);
|
||||
}
|
||||
|
||||
QTEST_MAIN(ChatDocumentHandlerTest)
|
||||
#include "chatdocumenthandlertest.moc"
|
||||
@@ -1,88 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtTest
|
||||
|
||||
import org.kde.neochat.libneochat
|
||||
|
||||
import NeoChatTestUtils
|
||||
|
||||
TestCase {
|
||||
name: "ChatKeyHelperTest"
|
||||
|
||||
TextEdit {
|
||||
id: textEdit
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
event.accepted = testHelper.keyHelper.handleKey(event.key, event.modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
ChatTextItemHelper {
|
||||
id: textItemHelper
|
||||
|
||||
textItem: textEdit
|
||||
}
|
||||
|
||||
ChatKeyHelperTestHelper {
|
||||
id: testHelper
|
||||
|
||||
textItem: textItemHelper
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyUp
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledUp"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyDown
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledDown"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyDelete
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledDelete"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyBackSpace
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledBackspace"
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
textEdit.clear();
|
||||
spyUp.clear();
|
||||
spyDown.clear();
|
||||
spyDelete.clear();
|
||||
spyBackSpace.clear();
|
||||
textEdit.forceActiveFocus();
|
||||
}
|
||||
|
||||
function cleanupTestCase(): void {
|
||||
testHelper.textItem = null;
|
||||
textItemHelper.textItem = null;
|
||||
}
|
||||
|
||||
function test_upDown(): void {
|
||||
textEdit.insert(0, "line 1\nline 2\nline 3")
|
||||
textEdit.cursorPosition = 0;
|
||||
keyClick(Qt.Key_Up);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 0);
|
||||
keyClick(Qt.Key_Down);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 0);
|
||||
keyClick(Qt.Key_Down);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 0);
|
||||
keyClick(Qt.Key_Down);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocumentFragment>
|
||||
|
||||
#include "chatkeyhelper.h"
|
||||
#include "chattextitemhelper.h"
|
||||
|
||||
class ChatKeyHelperTestHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(ChatTextItemHelper *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged)
|
||||
|
||||
Q_PROPERTY(ChatKeyHelper *keyHelper READ keyHelper CONSTANT)
|
||||
|
||||
public:
|
||||
explicit ChatKeyHelperTestHelper(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
, m_keyHelper(new ChatKeyHelper(this))
|
||||
{
|
||||
}
|
||||
|
||||
ChatTextItemHelper *textItem() const
|
||||
{
|
||||
return m_keyHelper->textItem();
|
||||
}
|
||||
void setTextItem(ChatTextItemHelper *textItem)
|
||||
{
|
||||
if (textItem == m_keyHelper->textItem()) {
|
||||
return;
|
||||
}
|
||||
m_keyHelper->setTextItem(textItem);
|
||||
Q_EMIT textItemChanged();
|
||||
}
|
||||
|
||||
ChatKeyHelper *keyHelper() const
|
||||
{
|
||||
return m_keyHelper;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
|
||||
private:
|
||||
QPointer<ChatKeyHelper> m_keyHelper;
|
||||
};
|
||||
@@ -1,173 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtTest
|
||||
|
||||
import org.kde.neochat.libneochat
|
||||
|
||||
import NeoChatTestUtils
|
||||
|
||||
TestCase {
|
||||
name: "ChatMarkdownHelperTest"
|
||||
|
||||
TextEdit {
|
||||
id: textEdit
|
||||
|
||||
textFormat: TextEdit.RichText
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: textEdit2
|
||||
}
|
||||
|
||||
ChatMarkdownHelperTestWrapper {
|
||||
id: chatMarkdownHelper
|
||||
|
||||
textItem: textEdit
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyItem
|
||||
target: chatMarkdownHelper
|
||||
signalName: "textItemChanged"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyUnhandledFormat
|
||||
target: chatMarkdownHelper
|
||||
signalName: "unhandledBlockFormat"
|
||||
}
|
||||
|
||||
function initTestCase(): void {
|
||||
textEdit.forceActiveFocus();
|
||||
}
|
||||
|
||||
function cleanup(): void {
|
||||
chatMarkdownHelper.clear();
|
||||
compare(chatMarkdownHelper.checkText(""), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
compare(textEdit.cursorPosition, 0);
|
||||
}
|
||||
|
||||
function test_item(): void {
|
||||
spyItem.clear();
|
||||
compare(chatMarkdownHelper.textItem, textEdit);
|
||||
chatMarkdownHelper.textItem = textEdit2;
|
||||
compare(chatMarkdownHelper.textItem, textEdit2);
|
||||
chatMarkdownHelper.textItem = textEdit;
|
||||
compare(chatMarkdownHelper.textItem, textEdit);
|
||||
}
|
||||
|
||||
function test_textFormat_data() {
|
||||
return [
|
||||
{tag: "bold", input: "**b** ", outText: ["*", "**", "b", "b*", "b**", "b "], outFormats: [[], [], [RichFormat.Bold], [RichFormat.Bold], [RichFormat.Bold], []], unhandled: 0},
|
||||
{tag: "italic", input: "*i* ", outText: ["*", "i", "i*", "i "], outFormats: [[], [RichFormat.Italic], [RichFormat.Italic], []], unhandled: 0},
|
||||
{tag: "heading 1", input: "# h", outText: ["#", "# ", "h"], outFormats: [[], [], [RichFormat.Bold, RichFormat.Heading1]], unhandled: 0},
|
||||
{tag: "heading 2", input: "## h", outText: ["#", "##", "## ", "h"], outFormats: [[], [], [], [RichFormat.Bold, RichFormat.Heading2]], unhandled: 0},
|
||||
{tag: "heading 3", input: "### h", outText: ["#", "##", "###", "### ", "h"], outFormats: [[], [], [], [], [RichFormat.Bold, RichFormat.Heading3]], unhandled: 0},
|
||||
{tag: "heading 4", input: "#### h", outText: ["#", "##", "###", "####", "#### ", "h"], outFormats: [[], [], [], [], [], [RichFormat.Bold, RichFormat.Heading4]], unhandled: 0},
|
||||
{tag: "heading 5", input: "##### h", outText: ["#", "##", "###", "####", "#####", "##### ", "h"], outFormats: [[], [], [], [], [], [], [RichFormat.Bold, RichFormat.Heading5]], unhandled: 0},
|
||||
{tag: "heading 6", input: "###### h", outText: ["#", "##", "###", "####", "#####", "######", "###### ", "h"], outFormats: [[], [], [], [], [], [] ,[], [RichFormat.Bold, RichFormat.Heading6]], unhandled: 0},
|
||||
{tag: "quote", input: "> q", outText: [">", "> ", "q"], outFormats: [[], [], []], unhandled: 1},
|
||||
{tag: "quote - no space", input: ">q", outText: [">", "q"], outFormats: [[], [], []], unhandled: 1},
|
||||
{tag: "unorderedlist 1", input: "* l", outText: ["*", "* ", "l"], outFormats: [[], [], [RichFormat.UnorderedList]], unhandled: 0},
|
||||
{tag: "unorderedlist 2", input: "- l", outText: ["-", "- ", "l"], outFormats: [[], [], [RichFormat.UnorderedList]], unhandled: 0},
|
||||
{tag: "orderedlist 1", input: "1. l", outText: ["1", "1.", "1. ", "l"], outFormats: [[], [], [], [RichFormat.OrderedList]], unhandled: 0},
|
||||
{tag: "orderedlist 2", input: "1) l", outText: ["1", "1)", "1) ", "l"], outFormats: [[], [], [], [RichFormat.OrderedList]], unhandled: 0},
|
||||
{tag: "inline code", input: "`c` ", outText: ["`", "c", "c`", "c "], outFormats: [[], [RichFormat.InlineCode], [RichFormat.InlineCode], []], unhandled: 0},
|
||||
{tag: "code", input: "``` ", outText: ["`", "``", "```", " "], outFormats: [[], [], [], []], unhandled: 1},
|
||||
{tag: "strikethrough", input: "~~s~~ ", outText: ["~", "~~", "s", "s~", "s~~", "s "], outFormats: [[], [], [RichFormat.Strikethrough], [RichFormat.Strikethrough], [RichFormat.Strikethrough], []], unhandled: 0},
|
||||
{tag: "underline", input: "_u_ ", outText: ["_", "u", "u_", "u "], outFormats: [[], [RichFormat.Underline], [RichFormat.Underline], []], unhandled: 0},
|
||||
{tag: "multiple closable", input: "***_~~t~~_*** ", outText: ["*", "**", "*", "_", "~", "~~", "t", "t~", "t~~", "t_", "t*", "t**", "t*", "t "], outFormats: [[], [], [RichFormat.Bold], [RichFormat.Bold, RichFormat.Italic], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline, RichFormat.Strikethrough], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline, RichFormat.Strikethrough], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline, RichFormat.Strikethrough], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline], [RichFormat.Bold, RichFormat.Italic], [RichFormat.Bold, RichFormat.Italic], [RichFormat.Italic], []], unhandled: 0},
|
||||
{tag: "nonclosable closable", input: "* **b** ", outText: ["*", "* ", "*", "**", "b", "b*", "b**", "b "], outFormats: [[], [], [RichFormat.UnorderedList], [RichFormat.UnorderedList], [RichFormat.Bold, RichFormat.UnorderedList], [RichFormat.Bold, RichFormat.UnorderedList], [RichFormat.Bold, RichFormat.UnorderedList], [RichFormat.UnorderedList]], unhandled: 0},
|
||||
{tag: "not at line start", input: " 1) ", outText: [" ", " 1", " 1)", " 1) "], outFormats: [[], [], [], []], unhandled: 0},
|
||||
]
|
||||
}
|
||||
|
||||
function test_textFormat(data): void {
|
||||
spyUnhandledFormat.clear();
|
||||
compare(spyUnhandledFormat.count, 0);
|
||||
|
||||
for (let i = 0; i < data.input.length; i++) {
|
||||
keyClick(data.input[i]);
|
||||
compare(chatMarkdownHelper.checkText(data.outText[i]), true);
|
||||
compare(chatMarkdownHelper.checkFormats(data.outFormats[i]), true);
|
||||
}
|
||||
|
||||
compare(spyUnhandledFormat.count, data.unhandled);
|
||||
}
|
||||
|
||||
function test_backspace(): void {
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("*"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("**"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
keyClick("b");
|
||||
compare(chatMarkdownHelper.checkText("b"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("o");
|
||||
compare(chatMarkdownHelper.checkText("bo"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("l");
|
||||
compare(chatMarkdownHelper.checkText("bol"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("d");
|
||||
compare(chatMarkdownHelper.checkText("bold"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick(Qt.Key_Backspace);
|
||||
compare(chatMarkdownHelper.checkText("bol"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick(Qt.Key_Backspace);
|
||||
compare(chatMarkdownHelper.checkText("bo"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("bo*"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("bo**"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick(" ");
|
||||
compare(chatMarkdownHelper.checkText("bo "), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
}
|
||||
|
||||
function test_cursorMove(): void {
|
||||
keyClick("t");
|
||||
keyClick("e");
|
||||
keyClick("s");
|
||||
keyClick("t");
|
||||
compare(chatMarkdownHelper.checkText("test"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
keyClick("*");
|
||||
keyClick("*");
|
||||
keyClick("b");
|
||||
compare(chatMarkdownHelper.checkText("testb"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
textEdit.cursorPosition = 2;
|
||||
keyClick("*");
|
||||
keyClick("*");
|
||||
keyClick("b");
|
||||
compare(chatMarkdownHelper.checkText("tebstb"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
}
|
||||
|
||||
function test_insertText(): void {
|
||||
textEdit.insert(0, "test");
|
||||
compare(chatMarkdownHelper.checkText("test"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
textEdit.insert(4, "**b");
|
||||
compare(chatMarkdownHelper.checkText("test**b"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
|
||||
textEdit.clear();
|
||||
textEdit.insert(0, "test");
|
||||
compare(chatMarkdownHelper.checkText("test"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
textEdit.insert(2, "**b");
|
||||
compare(chatMarkdownHelper.checkText("te**bst"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QTextCursor>
|
||||
|
||||
#include "chatmarkdownhelper.h"
|
||||
#include "chattextitemhelper.h"
|
||||
#include "enums/richformat.h"
|
||||
|
||||
class ChatMarkdownHelperTestWrapper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The QML text Item the ChatMerkdownHelper is handling.
|
||||
*/
|
||||
Q_PROPERTY(QQuickItem *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged)
|
||||
|
||||
public:
|
||||
explicit ChatMarkdownHelperTestWrapper(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
, m_chatMarkdownHelper(new ChatMarkdownHelper(this))
|
||||
, m_textItem(new ChatTextItemHelper(this))
|
||||
{
|
||||
m_chatMarkdownHelper->setTextItem(m_textItem);
|
||||
|
||||
connect(m_chatMarkdownHelper, &ChatMarkdownHelper::textItemChanged, this, &ChatMarkdownHelperTestWrapper::textItemChanged);
|
||||
connect(m_chatMarkdownHelper, &ChatMarkdownHelper::unhandledBlockFormat, this, &ChatMarkdownHelperTestWrapper::unhandledBlockFormat);
|
||||
}
|
||||
|
||||
QQuickItem *textItem() const
|
||||
{
|
||||
return m_textItem->textItem();
|
||||
}
|
||||
void setTextItem(QQuickItem *textItem)
|
||||
{
|
||||
m_textItem->setTextItem(textItem);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkText(const QString &text)
|
||||
{
|
||||
const auto doc = m_textItem->document();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
return text == doc->toPlainText();
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkFormats(QList<RichFormat::Format> formats)
|
||||
{
|
||||
const auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return false;
|
||||
}
|
||||
return RichFormat::formatsAtCursor(cursor) == formats;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void clear()
|
||||
{
|
||||
auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return;
|
||||
}
|
||||
cursor.select(QTextCursor::Document);
|
||||
cursor.removeSelectedText();
|
||||
cursor.setBlockCharFormat(RichFormat::charFormatForFormat(RichFormat::Paragraph));
|
||||
cursor.setBlockFormat(RichFormat::blockFormatForFormat(RichFormat::Paragraph));
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
void unhandledBlockFormat(RichFormat::Format format);
|
||||
|
||||
private:
|
||||
QPointer<ChatMarkdownHelper> m_chatMarkdownHelper;
|
||||
QPointer<ChatTextItemHelper> m_textItem;
|
||||
};
|
||||
@@ -1,301 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtTest
|
||||
|
||||
import org.kde.neochat.libneochat
|
||||
|
||||
import NeoChatTestUtils
|
||||
|
||||
TestCase {
|
||||
name: "ChatTextItemHelperTest"
|
||||
|
||||
TextEdit {
|
||||
id: textEdit
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: textEdit2
|
||||
}
|
||||
|
||||
ChatTextItemHelper {
|
||||
id: textItemHelper
|
||||
|
||||
textItem: textEdit
|
||||
}
|
||||
|
||||
ChatTextItemHelperTestHelper {
|
||||
id: testHelper
|
||||
|
||||
textItem: textItemHelper
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyItem
|
||||
target: textItemHelper
|
||||
signalName: "textItemChanged"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyContentsChanged
|
||||
target: textItemHelper
|
||||
signalName: "contentsChanged"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyContentsChange
|
||||
target: textItemHelper
|
||||
signalName: "contentsChange"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyCursor
|
||||
target: textItemHelper
|
||||
signalName: "cursorPositionChanged"
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
testHelper.setFixedChars("", "");
|
||||
textEdit.clear();
|
||||
textEdit2.clear();
|
||||
spyItem.clear();
|
||||
spyContentsChange.clear();
|
||||
spyContentsChanged.clear();
|
||||
spyCursor.clear();
|
||||
}
|
||||
|
||||
function cleanupTestCase(): void {
|
||||
testHelper.textItem = null;
|
||||
textItemHelper.textItem = null;
|
||||
}
|
||||
|
||||
function test_item(): void {
|
||||
compare(textItemHelper.textItem, textEdit);
|
||||
compare(spyItem.count, 0);
|
||||
textItemHelper.textItem = textEdit2;
|
||||
compare(textItemHelper.textItem, textEdit2);
|
||||
compare(spyItem.count, 1);
|
||||
textItemHelper.textItem = textEdit;
|
||||
compare(textItemHelper.textItem, textEdit);
|
||||
compare(spyItem.count, 2);
|
||||
}
|
||||
|
||||
function test_fixedChars(): void {
|
||||
textEdit.forceActiveFocus();
|
||||
testHelper.setFixedChars("1", "2");
|
||||
compare(textEdit.text, "12");
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 0);
|
||||
keyClick("b");
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 2);
|
||||
compare(spyCursor.count, 1);
|
||||
keyClick(Qt.Key_Left);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 2);
|
||||
keyClick(Qt.Key_Left);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 3);
|
||||
keyClick(Qt.Key_Right);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 2);
|
||||
compare(spyCursor.count, 4);
|
||||
keyClick(Qt.Key_Right);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 2);
|
||||
compare(spyCursor.count, 5);
|
||||
}
|
||||
|
||||
function test_document(): void {
|
||||
// We can't get to the QTextDocument from QML so we have to use a helper function.
|
||||
compare(testHelper.compareDocuments(textEdit.textDocument), true);
|
||||
|
||||
textEdit.insert(0, "test text");
|
||||
compare(testHelper.lineCount(), 1);
|
||||
textEdit.insert(textEdit.text.length, "\ntest text");
|
||||
compare(testHelper.lineCount(), 2);
|
||||
textEdit.clear()
|
||||
compare(textEdit.text.length, 0);
|
||||
}
|
||||
|
||||
function test_takeFirstBlock(): void {
|
||||
textEdit.insert(0, "test text");
|
||||
compare(testHelper.firstBlockText(), "test text");
|
||||
compare(textEdit.text.length, 0);
|
||||
textEdit.insert(0, "test text\nmore test text");
|
||||
compare(testHelper.firstBlockText(), "test text");
|
||||
compare(textEdit.text, "more test text");
|
||||
compare(testHelper.firstBlockText(), "more test text");
|
||||
compare(textEdit.text, "");
|
||||
compare(textEdit.text.length, 0);
|
||||
}
|
||||
|
||||
function test_fillFragments(): void {
|
||||
textEdit.insert(0, "before fragment\nmid fragment\nafter fragment");
|
||||
compare(testHelper.checkFragments("before fragment\nmid fragment", "after fragment", ""), true);
|
||||
textEdit.clear();
|
||||
textEdit.insert(0, "before fragment\nmid fragment\nafter fragment");
|
||||
textEdit.cursorPosition = 16;
|
||||
compare(testHelper.checkFragments("before fragment", "mid fragment", "after fragment"), true);
|
||||
textEdit.clear();
|
||||
textEdit.insert(0, "before fragment\nmid fragment\nafter fragment");
|
||||
textEdit.cursorPosition = 29;
|
||||
compare(testHelper.checkFragments("before fragment\nmid fragment", "after fragment", ""), true);
|
||||
textEdit.clear();
|
||||
}
|
||||
|
||||
function test_insertFragment(): void {
|
||||
testHelper.insertFragment("test text");
|
||||
compare(textEdit.text, "test text");
|
||||
compare(textEdit.cursorPosition, 9);
|
||||
testHelper.insertFragment("beginning ", 1);
|
||||
compare(textEdit.text, "beginning test text");
|
||||
compare(textEdit.cursorPosition, 10);
|
||||
testHelper.insertFragment(" end", 2);
|
||||
compare(textEdit.text, "beginning test text end");
|
||||
compare(textEdit.cursorPosition, 23);
|
||||
textEdit.clear();
|
||||
|
||||
testHelper.insertFragment("test text", 0, true);
|
||||
compare(textEdit.text, "test text");
|
||||
compare(textEdit.cursorPosition, 0);
|
||||
}
|
||||
|
||||
function test_cursor(): void {
|
||||
// We can't get to the QTextCursor from QML so we have to use a helper function.
|
||||
compare(testHelper.compareCursor(textEdit.cursorPosition, textEdit.selectionStart, textEdit.selectionEnd), true);
|
||||
compare(textEdit.cursorPosition, testHelper.cursorPosition());
|
||||
// Check we get the appropriate content and cursor change signals when inserting text.
|
||||
textEdit.insert(0, "test text")
|
||||
compare(spyContentsChange.count, 1);
|
||||
compare(spyContentsChange.signalArguments[0][0], 0);
|
||||
compare(spyContentsChange.signalArguments[0][1], 0);
|
||||
compare(spyContentsChange.signalArguments[0][2], 9);
|
||||
compare(spyContentsChanged.count, 1);
|
||||
compare(spyCursor.count, 1);
|
||||
compare(spyCursor.signalArguments[0][0], true);
|
||||
compare(testHelper.compareCursor(textEdit.cursorPosition, textEdit.selectionStart, textEdit.selectionEnd), true);
|
||||
compare(textEdit.cursorPosition, testHelper.cursorPosition());
|
||||
// Check we get only get a cursor change signal when moving the cursor.
|
||||
textEdit.cursorPosition = 4;
|
||||
compare(spyContentsChanged.count, 1);
|
||||
compare(spyCursor.count, 2);
|
||||
compare(spyCursor.signalArguments[1][0], false);
|
||||
textEdit.selectAll();
|
||||
compare(spyContentsChanged.count, 1);
|
||||
compare(spyCursor.count, 2);
|
||||
compare(testHelper.compareCursor(textEdit.cursorPosition, textEdit.selectionStart, textEdit.selectionEnd), true);
|
||||
compare(textEdit.cursorPosition, testHelper.cursorPosition());
|
||||
// Check we get the appropriate content and cursor change signals when removing text.
|
||||
textEdit.clear();
|
||||
compare(spyContentsChange.count, 2);
|
||||
compare(spyContentsChange.signalArguments[1][0], 0);
|
||||
compare(spyContentsChange.signalArguments[1][1], 9);
|
||||
compare(spyContentsChange.signalArguments[1][2], 0);
|
||||
compare(spyContentsChanged.count, 2);
|
||||
compare(spyCursor.count, 3);
|
||||
compare(spyCursor.signalArguments[2][0], true);
|
||||
}
|
||||
|
||||
function test_setCursor(): void {
|
||||
textEdit.insert(0, "test text");
|
||||
compare(textEdit.cursorPosition, 9);
|
||||
compare(spyCursor.count, 1);
|
||||
testHelper.setCursorPosition(5);
|
||||
compare(textEdit.cursorPosition, 5);
|
||||
compare(spyCursor.count, 2);
|
||||
testHelper.setCursorPosition(1);
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 3);
|
||||
|
||||
textEdit.cursorVisible = false;
|
||||
compare(textEdit.cursorVisible, false);
|
||||
testHelper.setCursorVisible(true);
|
||||
compare(textEdit.cursorVisible, true);
|
||||
testHelper.setCursorVisible(false);
|
||||
compare(textEdit.cursorVisible, false);
|
||||
}
|
||||
|
||||
function test_setCursorFromTextItem(): void {
|
||||
textEdit.insert(0, "line 1\nline 2");
|
||||
textEdit2.insert(0, "line 1\nline 2");
|
||||
testHelper.setCursorFromTextItem(textEdit2, false, 0);
|
||||
compare(textEdit.cursorPosition, 7);
|
||||
testHelper.setCursorFromTextItem(textEdit2, true, 7);
|
||||
compare(textEdit.cursorPosition, 0);
|
||||
testHelper.setCursorFromTextItem(textEdit2, false, 1);
|
||||
compare(textEdit.cursorPosition, 8);
|
||||
testHelper.setCursorFromTextItem(textEdit2, true, 8);
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
|
||||
testHelper.setFixedChars("1", "2");
|
||||
testHelper.setCursorFromTextItem(textEdit2, false, 0);
|
||||
compare(textEdit.cursorPosition, 8);
|
||||
testHelper.setCursorFromTextItem(textEdit2, true, 7);
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
}
|
||||
|
||||
function test_mergeFormat(): void {
|
||||
textEdit.insert(0, "lots of text");
|
||||
testHelper.setCursorPosition(0);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Bold);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Italic);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold, RichFormat.Italic]), true);
|
||||
testHelper.setCursorPosition(6);
|
||||
compare(testHelper.checkFormatsAtCursor([]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Underline);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Underline]), true);
|
||||
testHelper.setCursorPosition(9);
|
||||
compare(testHelper.checkFormatsAtCursor([]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Strikethrough);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Strikethrough]), true);
|
||||
textEdit.clear();
|
||||
|
||||
textEdit.insert(0, "heading");
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Heading1);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold, RichFormat.Heading1]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Heading2);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold, RichFormat.Heading2]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Paragraph);
|
||||
compare(testHelper.checkFormatsAtCursor([]), true);
|
||||
textEdit.clear();
|
||||
|
||||
textEdit.insert(0, "text");
|
||||
testHelper.mergeFormatOnCursor(RichFormat.UnorderedList);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.UnorderedList]), true);
|
||||
compare(testHelper.markdownText(), "- text");
|
||||
testHelper.mergeFormatOnCursor(RichFormat.OrderedList);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.OrderedList]), true);
|
||||
compare(testHelper.markdownText(), "1. text");
|
||||
textEdit.clear();
|
||||
}
|
||||
|
||||
function test_list(): void {
|
||||
compare(testHelper.canIndentListMoreAtCursor(), true);
|
||||
testHelper.indentListMoreAtCursor();
|
||||
compare(testHelper.canIndentListMoreAtCursor(), true);
|
||||
testHelper.indentListMoreAtCursor();
|
||||
compare(testHelper.canIndentListMoreAtCursor(), true);
|
||||
testHelper.indentListMoreAtCursor();
|
||||
compare(testHelper.canIndentListMoreAtCursor(), false);
|
||||
|
||||
compare(testHelper.canIndentListLessAtCursor(), true);
|
||||
testHelper.indentListLessAtCursor();
|
||||
compare(testHelper.canIndentListLessAtCursor(), true);
|
||||
testHelper.indentListLessAtCursor();
|
||||
compare(testHelper.canIndentListLessAtCursor(), true);
|
||||
testHelper.indentListLessAtCursor();
|
||||
compare(testHelper.canIndentListLessAtCursor(), false);
|
||||
}
|
||||
|
||||
function test_forceActiveFocus(): void {
|
||||
textEdit2.forceActiveFocus();
|
||||
compare(textEdit.activeFocus, false);
|
||||
testHelper.forceActiveFocus();
|
||||
compare(textEdit.activeFocus, true);
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocumentFragment>
|
||||
|
||||
#include "chattextitemhelper.h"
|
||||
|
||||
class ChatTextItemHelperTestHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The QML text Item the TextItemHelper is handling.
|
||||
*/
|
||||
Q_PROPERTY(ChatTextItemHelper *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged)
|
||||
|
||||
public:
|
||||
explicit ChatTextItemHelperTestHelper(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ChatTextItemHelper *textItem() const
|
||||
{
|
||||
return m_textItem;
|
||||
}
|
||||
void setTextItem(ChatTextItemHelper *textItem)
|
||||
{
|
||||
if (textItem == m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem = textItem;
|
||||
Q_EMIT textItemChanged();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setFixedChars(const QString &startChars, const QString &endChars)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->setFixedChars(startChars, endChars);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool compareDocuments(QQuickTextDocument *document)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
return document->textDocument() == m_textItem->document();
|
||||
}
|
||||
|
||||
Q_INVOKABLE int lineCount()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return -1;
|
||||
}
|
||||
return m_textItem->lineCount();
|
||||
}
|
||||
|
||||
Q_INVOKABLE QString firstBlockText()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return {};
|
||||
}
|
||||
return m_textItem->takeFirstBlock().toPlainText();
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkFragments(const QString &before, const QString &mid, const QString &after)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasBefore = false;
|
||||
QTextDocumentFragment midFragment;
|
||||
std::optional<QTextDocumentFragment> afterFragment = std::nullopt;
|
||||
m_textItem->fillFragments(hasBefore, midFragment, afterFragment);
|
||||
|
||||
return hasBefore && m_textItem->document()->toPlainText() == before && midFragment.toPlainText() == mid && after.isEmpty()
|
||||
? !afterFragment
|
||||
: afterFragment->toPlainText() == after;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void insertFragment(const QString &text, ChatTextItemHelper::InsertPosition position = ChatTextItemHelper::Cursor, bool keepPosition = false)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
const auto fragment = QTextDocumentFragment::fromPlainText(text);
|
||||
m_textItem->insertFragment(fragment, position, keepPosition);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool compareCursor(int cursorPosition, int selectionStart, int selectionEnd)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
const auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return false;
|
||||
}
|
||||
const auto posSame = cursor.position() == cursorPosition;
|
||||
const auto startSame = cursor.selectionStart() == selectionStart;
|
||||
const auto endSame = cursor.selectionEnd() == selectionEnd;
|
||||
return posSame && startSame && endSame;
|
||||
}
|
||||
|
||||
Q_INVOKABLE int cursorPosition() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return -1;
|
||||
}
|
||||
return *m_textItem->cursorPosition();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setCursorPosition(int pos)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->setCursorPosition(pos);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setCursorVisible(bool visible)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->setCursorVisible(visible);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setCursorFromTextItem(QQuickItem *item, bool infront, int cursorPos)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
const auto textItem = new ChatTextItemHelper(this);
|
||||
textItem->setTextItem(item);
|
||||
textItem->setCursorPosition(cursorPos);
|
||||
m_textItem->setCursorFromTextItem(textItem, infront);
|
||||
textItem->deleteLater();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void mergeFormatOnCursor(RichFormat::Format format)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->mergeFormatOnCursor(format);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkFormatsAtCursor(QList<RichFormat::Format> formats)
|
||||
{
|
||||
const auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return false;
|
||||
}
|
||||
return RichFormat::formatsAtCursor(cursor) == formats;
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool canIndentListMoreAtCursor() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
return m_textItem->canIndentListMoreAtCursor();
|
||||
}
|
||||
Q_INVOKABLE bool canIndentListLessAtCursor() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
return m_textItem->canIndentListLessAtCursor();
|
||||
}
|
||||
Q_INVOKABLE void indentListMoreAtCursor()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->indentListMoreAtCursor();
|
||||
}
|
||||
Q_INVOKABLE void indentListLessAtCursor()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->indentListLessAtCursor();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void forceActiveFocus() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->forceActiveFocus();
|
||||
}
|
||||
|
||||
Q_INVOKABLE QString markdownText() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return {};
|
||||
}
|
||||
return m_textItem->markdownText();
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
|
||||
private:
|
||||
QPointer<ChatTextItemHelper> m_textItem;
|
||||
};
|
||||
@@ -40,6 +40,7 @@ private Q_SLOTS:
|
||||
void nullSingleLineDisplayName();
|
||||
void time();
|
||||
void nullTime();
|
||||
void timeString();
|
||||
void highlighted();
|
||||
void nullHighlighted();
|
||||
void hidden();
|
||||
@@ -99,12 +100,12 @@ void EventHandlerTest::time()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
QCOMPARE(EventHandler::dateTime(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
|
||||
QCOMPARE(EventHandler::time(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
|
||||
|
||||
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
|
||||
QCOMPARE(room->pendingEvents().size(), 1);
|
||||
const auto pendingIt = room->findPendingEvent(txID);
|
||||
QCOMPARE(EventHandler::dateTime(room, pendingIt->event(), true), pendingIt->lastUpdated());
|
||||
QCOMPARE(EventHandler::time(room, pendingIt->event(), true), pendingIt->lastUpdated());
|
||||
|
||||
room->discardMessage(txID);
|
||||
QCOMPARE(room->pendingEvents().size(), 0);
|
||||
@@ -113,10 +114,39 @@ void EventHandlerTest::time()
|
||||
void EventHandlerTest::nullTime()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "time called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::dateTime(nullptr, nullptr), QDateTime());
|
||||
QCOMPARE(EventHandler::time(nullptr, nullptr), QDateTime());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "time called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::dateTime(room, nullptr), QDateTime());
|
||||
QCOMPARE(EventHandler::time(room, nullptr), QDateTime());
|
||||
}
|
||||
|
||||
void EventHandlerTest::timeString()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
KFormat format;
|
||||
|
||||
QCOMPARE(EventHandler::timeString(room, event, false),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, event, true),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(u"hh:mm"_s));
|
||||
|
||||
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
|
||||
QCOMPARE(room->pendingEvents().size(), 1);
|
||||
const auto pendingIt = room->findPendingEvent(txID);
|
||||
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::ShortFormat, true),
|
||||
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::ShortFormat, true),
|
||||
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::LongFormat, true),
|
||||
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::LongFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::LongFormat, true),
|
||||
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::LongFormat));
|
||||
|
||||
room->discardMessage(txID);
|
||||
QCOMPARE(room->pendingEvents().size(), 0);
|
||||
}
|
||||
|
||||
void EventHandlerTest::highlighted()
|
||||
|
||||
@@ -19,7 +19,13 @@ class LinkPreviewerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void linkPreviewsMatch_data();
|
||||
void linkPreviewsMatch();
|
||||
|
||||
@@ -30,6 +36,12 @@ private Q_SLOTS:
|
||||
void linkPreviewsReject();
|
||||
};
|
||||
|
||||
void LinkPreviewerTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(u"@bob:example.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"!test:example.org"_s);
|
||||
}
|
||||
|
||||
void LinkPreviewerTest::linkPreviewsMatch_data()
|
||||
{
|
||||
QTest::addColumn<QString>("inputString");
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <Quotient/roommember.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "models/eventmessagecontentmodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "testutils.h"
|
||||
@@ -39,17 +39,17 @@ void MessageContentModelTest::initTestCase()
|
||||
void MessageContentModelTest::missingEvent()
|
||||
{
|
||||
auto room = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
|
||||
auto model1 = EventMessageContentModel(room, u"$153456789:example.org"_s);
|
||||
auto model1 = MessageContentModel(room, u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(model1.rowCount(), 1);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading…"_s);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
|
||||
|
||||
auto model2 = EventMessageContentModel(room, u"$153456789:example.org"_s, true);
|
||||
auto model2 = MessageContentModel(room, u"$153456789:example.org"_s, true);
|
||||
|
||||
QCOMPARE(model2.rowCount(), 1);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply…"_s);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
|
||||
|
||||
room->syncNewEvents(u"test-min-sync.json"_s);
|
||||
QCOMPARE(model1.rowCount(), 2);
|
||||
|
||||
@@ -1,621 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QAbstractItemModelTester>
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
#include <QVariantList>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "contentprovider.h"
|
||||
#include "enums/powerlevel.h"
|
||||
#include "enums/roomsortparameter.h"
|
||||
#include "models/accountemoticonmodel.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/commonroomsmodel.h"
|
||||
#include "models/completionmodel.h"
|
||||
#include "models/completionproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/devicesproxymodel.h"
|
||||
#include "models/emojimodel.h"
|
||||
#include "models/emoticonfiltermodel.h"
|
||||
#include "models/eventmessagecontentmodel.h"
|
||||
#include "models/imagepacksmodel.h"
|
||||
#include "models/linemodel.h"
|
||||
#include "models/livelocationsmodel.h"
|
||||
#include "models/locationsmodel.h"
|
||||
#include "models/messagecontentfiltermodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "models/notificationsmodel.h"
|
||||
#include "models/permissionsmodel.h"
|
||||
#include "models/pinnedmessagemodel.h"
|
||||
#include "models/pollanswermodel.h"
|
||||
#include "models/publicroomlistmodel.h"
|
||||
#include "models/pushrulemodel.h"
|
||||
#include "models/readmarkermodel.h"
|
||||
#include "models/roomsortparametermodel.h"
|
||||
#include "models/searchmodel.h"
|
||||
#include "models/serverlistmodel.h"
|
||||
#include "models/spacechildrenmodel.h"
|
||||
#include "models/spacechildsortfiltermodel.h"
|
||||
#include "models/statefiltermodel.h"
|
||||
#include "models/statekeysmodel.h"
|
||||
#include "models/statemodel.h"
|
||||
#include "models/stickermodel.h"
|
||||
#include "models/threadmodel.h"
|
||||
#include "models/threepidmodel.h"
|
||||
#include "models/userdirectorylistmodel.h"
|
||||
#include "models/userfiltermodel.h"
|
||||
#include "models/webshortcutmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "pollhandler.h"
|
||||
#include "roommanager.h"
|
||||
#include "server.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
// TODO: Add data to all models as relevant.
|
||||
|
||||
// Performs basic tests on all models in NeoChat
|
||||
// When adding a new test, create the model first, then the tester, then initialize the model (e.g., setConnection and setRoom).
|
||||
// That way, the models are also tested for whether they can handle having no connection etc.
|
||||
class ModelTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
NeoChatConnection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
|
||||
QString eventId;
|
||||
|
||||
Server server;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void testRoomTreeModel();
|
||||
void testMessageContentModel();
|
||||
void testEventMessageContentModel();
|
||||
void testThreadModel();
|
||||
void testThreadFetchModel();
|
||||
void testThreadChatBarModel();
|
||||
void testReactionModel();
|
||||
void testPollAnswerModel();
|
||||
void testLineModel();
|
||||
void testSpaceChildrenModel();
|
||||
void testItineraryModel();
|
||||
void testPublicRoomListModel();
|
||||
void testMessageFilterModel();
|
||||
void testThreePIdModel();
|
||||
void testMediaMessageFilterModel();
|
||||
void testWebshortcutModel();
|
||||
void testTimelineMessageModel();
|
||||
void testReadMarkerModel();
|
||||
void testSearchModel();
|
||||
void testStateModel();
|
||||
void testTimelineModel();
|
||||
void testStateKeysModel();
|
||||
void testPinnedMessageModel();
|
||||
void testUserListModel();
|
||||
void testStickerModel();
|
||||
void testPowerLevelModel();
|
||||
void testImagePacksModel();
|
||||
void testCompletionModel();
|
||||
void testRoomListModel();
|
||||
void testCommonRoomsModel();
|
||||
void testNotificationsModel();
|
||||
void testLocationsModel();
|
||||
void testServerListModel();
|
||||
void testEmojiModel();
|
||||
void testCustomEmojiModel();
|
||||
void testPushRuleModel();
|
||||
void testActionsModel();
|
||||
void testDevicesModel();
|
||||
void testUserDirectoryListModel();
|
||||
void testAccountEmoticonModel();
|
||||
void testPermissionsModel();
|
||||
void testLiveLocationsModel();
|
||||
void testRoomSortParameterModel();
|
||||
void testSortFilterRoomTreeModel();
|
||||
void testSortFilterSpaceListModel();
|
||||
void testSortFilterRoomListModel();
|
||||
void testSpaceChildSortFilterModel();
|
||||
void testStateFilterModel();
|
||||
void testMessageContentFilterModel();
|
||||
void testUserFilterModel();
|
||||
void testEmoticonFilterModel();
|
||||
void testDevicesProxyModel();
|
||||
void testCompletionProxyModel();
|
||||
};
|
||||
|
||||
void ModelTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true, this);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
eventId = server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"asdf"_s},
|
||||
{u"m.relates_to"_s,
|
||||
QJsonObject{
|
||||
{u"event_id"_s, u"$GEucSt3TfVl6DVpKEyeOlRsXzjLv2ZCVgSQuQclFg1o"_s},
|
||||
{u"is_falling_back"_s, true},
|
||||
{u"m.in_reply_to"_s, QJsonObject{{u"event_id"_s, u"$GEucSt3TfVl6DVpKEyeOlRsXzjLv2ZCVgSQuQclFg1o"_s}}},
|
||||
{u"rel_type"_s, u"m.thread"_s},
|
||||
}},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
}
|
||||
|
||||
void ModelTest::testRoomTreeModel()
|
||||
{
|
||||
auto roomTreeModel = new RoomTreeModel(this);
|
||||
auto tester = new QAbstractItemModelTester(roomTreeModel, roomTreeModel);
|
||||
tester->setUseFetchMore(true);
|
||||
roomTreeModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testMessageContentModel()
|
||||
{
|
||||
auto contentModel = std::make_unique<MessageContentModel>(room, eventId);
|
||||
auto tester = new QAbstractItemModelTester(contentModel.get(), contentModel.get());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testEventMessageContentModel()
|
||||
{
|
||||
auto model = std::make_unique<EventMessageContentModel>(room, eventId);
|
||||
auto tester = new QAbstractItemModelTester(model.get(), model.get());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreadModel()
|
||||
{
|
||||
auto model = std::make_unique<ThreadModel>(eventId, room);
|
||||
auto tester = new QAbstractItemModelTester(model.get(), model.get());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreadFetchModel()
|
||||
{
|
||||
auto threadModel = std::make_unique<ThreadModel>(eventId, room);
|
||||
auto model = new ThreadFetchModel(threadModel.get());
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreadChatBarModel()
|
||||
{
|
||||
auto threadModel = std::make_unique<ThreadModel>(eventId, room);
|
||||
auto model = new ThreadChatBarModel(threadModel.get(), room);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testReactionModel()
|
||||
{
|
||||
auto messageContentModel = std::make_unique<MessageContentModel>(room);
|
||||
auto model = new ReactionModel(messageContentModel.get(), eventId, room);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testPollAnswerModel()
|
||||
{
|
||||
auto handler = std::make_unique<PollHandler>(room, eventId);
|
||||
auto model = new PollAnswerModel(handler.get());
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testLineModel()
|
||||
{
|
||||
auto model = new LineModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto document = new QTextDocument(this);
|
||||
model->setDocument(document);
|
||||
document->setPlainText(u"foo\nbar\n\nbaz"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testSpaceChildrenModel()
|
||||
{
|
||||
auto model = new SpaceChildrenModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSpace(room);
|
||||
}
|
||||
|
||||
void ModelTest::testItineraryModel()
|
||||
{
|
||||
auto model = new ItineraryModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testPublicRoomListModel()
|
||||
{
|
||||
auto model = new PublicRoomListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testMessageFilterModel()
|
||||
{
|
||||
auto timelineModel = new TimelineModel(this);
|
||||
auto model = new MessageFilterModel(this, timelineModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
timelineModel->setRoom(room);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreePIdModel()
|
||||
{
|
||||
auto model = new ThreePIdModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testMediaMessageFilterModel()
|
||||
{
|
||||
auto timelineModel = new TimelineModel(this);
|
||||
auto messageFilterModel = new MessageFilterModel(this, timelineModel);
|
||||
auto model = new MediaMessageFilterModel(this, messageFilterModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
timelineModel->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testWebshortcutModel()
|
||||
{
|
||||
auto model = new WebShortcutModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSelectedText(u"Foo"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testTimelineMessageModel()
|
||||
{
|
||||
auto model = new TimelineMessageModel();
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testReadMarkerModel()
|
||||
{
|
||||
auto model = std::make_unique<ReadMarkerModel>(eventId, room);
|
||||
auto tester = new QAbstractItemModelTester(model.get(), model.get());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testSearchModel()
|
||||
{
|
||||
auto model = new SearchModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSearchText(u"foo"_s);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testStateModel()
|
||||
{
|
||||
auto model = new StateModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testTimelineModel()
|
||||
{
|
||||
auto model = new TimelineModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testStateKeysModel()
|
||||
{
|
||||
auto model = new StateKeysModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setEventType(u"m.room.member"_s);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testPinnedMessageModel()
|
||||
{
|
||||
auto model = new PinnedMessageModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testUserListModel()
|
||||
{
|
||||
auto model = new UserListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testStickerModel()
|
||||
{
|
||||
auto model = new StickerModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setPackIndex(0);
|
||||
model->setRoom(room);
|
||||
auto imagePacksModel = new ImagePacksModel(this);
|
||||
model->setModel(imagePacksModel);
|
||||
imagePacksModel->setRoom(room);
|
||||
imagePacksModel->setShowEmoticons(true);
|
||||
imagePacksModel->setShowStickers(true);
|
||||
}
|
||||
|
||||
void ModelTest::testPowerLevelModel()
|
||||
{
|
||||
auto model = new PowerLevelModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testImagePacksModel()
|
||||
{
|
||||
auto model = new ImagePacksModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
model->setShowEmoticons(true);
|
||||
model->setShowStickers(true);
|
||||
}
|
||||
|
||||
void ModelTest::testCompletionModel()
|
||||
{
|
||||
auto model = new CompletionModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setAutoCompletionType(CompletionModel::Room);
|
||||
auto roomListModel = new RoomListModel(this);
|
||||
roomListModel->setConnection(connection);
|
||||
model->setRoomListModel(roomListModel);
|
||||
}
|
||||
|
||||
void ModelTest::testRoomListModel()
|
||||
{
|
||||
auto model = new RoomListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testCommonRoomsModel()
|
||||
{
|
||||
auto model = new CommonRoomsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
model->setUserId(u"@user:example.com"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testNotificationsModel()
|
||||
{
|
||||
auto model = new NotificationsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testLocationsModel()
|
||||
{
|
||||
auto model = new LocationsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testServerListModel()
|
||||
{
|
||||
auto model = new ServerListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testEmojiModel()
|
||||
{
|
||||
auto tester = new QAbstractItemModelTester(&EmojiModel::instance(), &EmojiModel::instance());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testCustomEmojiModel()
|
||||
{
|
||||
auto tester = new QAbstractItemModelTester(&CustomEmojiModel::instance(), &CustomEmojiModel::instance());
|
||||
tester->setUseFetchMore(true);
|
||||
CustomEmojiModel::instance().setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testPushRuleModel()
|
||||
{
|
||||
auto model = new PushRuleModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testActionsModel()
|
||||
{
|
||||
auto tester = new QAbstractItemModelTester(&ActionsModel::instance(), &ActionsModel::instance());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testDevicesModel()
|
||||
{
|
||||
auto model = new DevicesModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testUserDirectoryListModel()
|
||||
{
|
||||
auto model = new UserDirectoryListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
model->setSearchText(u"foo"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testAccountEmoticonModel()
|
||||
{
|
||||
auto model = new AccountEmoticonModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testPermissionsModel()
|
||||
{
|
||||
auto model = new PermissionsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testLiveLocationsModel()
|
||||
{
|
||||
auto model = new LiveLocationsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testRoomSortParameterModel()
|
||||
{
|
||||
auto model = new RoomSortParameterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testSortFilterRoomTreeModel()
|
||||
{
|
||||
auto sourceModel = new RoomTreeModel(this);
|
||||
auto model = new SortFilterRoomTreeModel(sourceModel, sourceModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
sourceModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testSortFilterSpaceListModel()
|
||||
{
|
||||
auto sourceModel = new RoomListModel(this);
|
||||
auto model = new SortFilterSpaceListModel(sourceModel, sourceModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
sourceModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testSortFilterRoomListModel()
|
||||
{
|
||||
auto sourceModel = new RoomListModel(this);
|
||||
auto model = new SortFilterRoomListModel(sourceModel, sourceModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
sourceModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testSpaceChildSortFilterModel()
|
||||
{
|
||||
auto model = new SpaceChildSortFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto spaceChildrenModel = new SpaceChildrenModel(this);
|
||||
model->setSourceModel(spaceChildrenModel);
|
||||
spaceChildrenModel->setSpace(nullptr);
|
||||
}
|
||||
|
||||
void ModelTest::testStateFilterModel()
|
||||
{
|
||||
auto model = new StateFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto stateModel = new StateModel(this);
|
||||
model->setSourceModel(stateModel);
|
||||
stateModel->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testMessageContentFilterModel()
|
||||
{
|
||||
auto model = new MessageContentFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSourceModel(ContentProvider::self().contentModelForEvent(room, eventId));
|
||||
}
|
||||
|
||||
void ModelTest::testUserFilterModel()
|
||||
{
|
||||
auto model = new UserFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto userListModel = new UserListModel(this);
|
||||
model->setSourceModel(userListModel);
|
||||
userListModel->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testEmoticonFilterModel()
|
||||
{
|
||||
auto model = new EmoticonFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto accountEmoticonModel = new AccountEmoticonModel(this);
|
||||
model->setSourceModel(accountEmoticonModel);
|
||||
model->setShowEmojis(true);
|
||||
model->setShowStickers(true);
|
||||
accountEmoticonModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testDevicesProxyModel()
|
||||
{
|
||||
auto model = new DevicesProxyModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto devicesModel = new DevicesModel(this);
|
||||
model->setSourceModel(devicesModel);
|
||||
devicesModel->setConnection(dynamic_cast<NeoChatConnection *>(connection));
|
||||
}
|
||||
|
||||
void ModelTest::testCompletionProxyModel()
|
||||
{
|
||||
auto model = new CompletionProxyModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSourceModel(&EmojiModel::instance());
|
||||
}
|
||||
|
||||
QTEST_MAIN(ModelTest)
|
||||
#include "modeltest.moc"
|
||||
@@ -9,10 +9,6 @@
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "server.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -22,8 +18,7 @@ class NeoChatRoomTest : public QObject {
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
Server server;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -32,27 +27,8 @@ private Q_SLOTS:
|
||||
|
||||
void NeoChatRoomTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true, this);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
|
||||
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
||||
}
|
||||
|
||||
void NeoChatRoomTest::eventTest()
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <quicktest.h>
|
||||
|
||||
QUICK_TEST_MAIN(NeoChat)
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "models/eventmessagecontentmodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -21,7 +21,7 @@ class ReactionModelTest : public QObject
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
EventMessageContentModel *parentModel;
|
||||
MessageContentModel *parentModel;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -34,7 +34,7 @@ void ReactionModelTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
|
||||
parentModel = new EventMessageContentModel(room, "123456"_L1);
|
||||
parentModel = new MessageContentModel(room, "123456"_L1);
|
||||
}
|
||||
|
||||
void ReactionModelTest::basicReaction()
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
#include <QVariantList>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "roommanager.h"
|
||||
|
||||
#include "server.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class RoomManagerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
NeoChatConnection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
|
||||
Server server;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void testMaximizeMedia();
|
||||
void testResolveMatrixLinks();
|
||||
};
|
||||
|
||||
void RoomManagerTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
QVERIFY(connection);
|
||||
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
RoomManager::instance().setConnection(connection);
|
||||
QSignalSpy roomSpy(&RoomManager::instance(), &RoomManager::currentRoomChanged);
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
QVERIFY(roomSpy.size() > 0);
|
||||
}
|
||||
|
||||
void RoomManagerTest::testMaximizeMedia()
|
||||
{
|
||||
QSignalSpy spy(&RoomManager::instance(), &RoomManager::showMaximizedMedia);
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for empty event id");
|
||||
RoomManager::instance().maximizeMedia(QString());
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for unknown event id \"Doesn't exist\"");
|
||||
RoomManager::instance().maximizeMedia(u"Doesn't exist"_s);
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
const auto eventWithoutMedia = server.sendEvent(room->id(),
|
||||
u"m.room.message"_s,
|
||||
QJsonObject({
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"format"_s, u"org.matrix.custom.html"_s},
|
||||
{u"formatted_body"_s, u"Foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
}));
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(eventWithoutMedia).toLatin1().data());
|
||||
RoomManager::instance().maximizeMedia(eventWithoutMedia);
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
// NOTE: This is supposed to test that maximizing pending media works correctly. This probably doesn't work in the UI yet, but at least the backend supports
|
||||
// it. If the server ever learns how to process events, this becomes pointless and we need to find a way of preventing *these* events from arriving
|
||||
auto pendingEventWithoutMedia = room->postText(u"Hello"_s);
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(pendingEventWithoutMedia).toLatin1().data());
|
||||
RoomManager::instance().maximizeMedia(pendingEventWithoutMedia);
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
const auto eventWithMedia = server.sendEvent(room->id(),
|
||||
u"m.room.message"_s,
|
||||
QJsonObject({
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"filename"_s, u"foo.jpg"_s},
|
||||
{u"info"_s,
|
||||
QJsonObject{
|
||||
{u"h"_s, 1000},
|
||||
{u"w"_s, 2000},
|
||||
{u"size"_s, 10000},
|
||||
{u"mimetype"_s, u"image/png"_s},
|
||||
}},
|
||||
{u"msgtype"_s, u"m.image"_s},
|
||||
{u"url"_s, u"mxc://foo.bar/asdf"_s},
|
||||
}));
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
RoomManager::instance().maximizeMedia(eventWithMedia);
|
||||
QVERIFY(spy.size() == 1);
|
||||
QVERIFY(spy[0][0] == 0);
|
||||
|
||||
auto pendingEventWithMedia = room->postJson(u"m.room.message"_s,
|
||||
QJsonObject({
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"filename"_s, u"foo.jpg"_s},
|
||||
{u"info"_s,
|
||||
QJsonObject{
|
||||
{u"h"_s, 1000},
|
||||
{u"w"_s, 2000},
|
||||
{u"size"_s, 10000},
|
||||
{u"mimetype"_s, u"image/png"_s},
|
||||
}},
|
||||
{u"msgtype"_s, u"m.image"_s},
|
||||
{u"url"_s, u"mxc://foo.bar/asdf"_s},
|
||||
}));
|
||||
RoomManager::instance().maximizeMedia(pendingEventWithMedia);
|
||||
QVERIFY(spy.size() == 2);
|
||||
QVERIFY(spy[1][0] == 0);
|
||||
}
|
||||
|
||||
void RoomManagerTest::testResolveMatrixLinks()
|
||||
{
|
||||
// Test if resolving a non-joined room will bring up the confirmation dialog.
|
||||
const QSignalSpy askToJoinSpy(&RoomManager::instance(), &RoomManager::askJoinRoom);
|
||||
RoomManager::instance().resolveResource(QStringLiteral("matrix:r/testbuild:matrix.org"), QStringLiteral("join"));
|
||||
QTRY_COMPARE(askToJoinSpy.size(), 1);
|
||||
}
|
||||
|
||||
QTEST_MAIN(RoomManagerTest)
|
||||
#include "roommanagertest.moc"
|
||||
@@ -4,12 +4,15 @@
|
||||
#include "server.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpServerResponder>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSslServer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <Quotient/networkaccessmanager.h>
|
||||
@@ -106,297 +109,127 @@ void Server::start()
|
||||
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
|
||||
QHttpServerRequest::Method::Post,
|
||||
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString(),
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
|
||||
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, this, &Server::sync);
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
|
||||
QMap<QString, QJsonArray> stateEvents;
|
||||
|
||||
for (const auto &[roomId, matrixId] : m_roomsToCreate) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, matrixId},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, matrixId},
|
||||
{u"state_key"_s, matrixId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
m_roomsToCreate.clear();
|
||||
for (const auto &roomId : m_invitedUsers.keys()) {
|
||||
const auto &values = m_invitedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_invitedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_bannedUsers.keys()) {
|
||||
const auto &values = m_bannedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_bannedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_joinedUsers.keys()) {
|
||||
const auto &values = m_joinedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_joinedUsers.clear();
|
||||
|
||||
QJsonObject rooms;
|
||||
for (const auto &roomId : stateEvents.keys()) {
|
||||
rooms[roomId] = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}}};
|
||||
}
|
||||
|
||||
responder.write(QJsonDocument(QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
|
||||
QSslConfiguration config;
|
||||
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
|
||||
void(key.open(QFile::ReadOnly));
|
||||
key.open(QFile::ReadOnly);
|
||||
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
|
||||
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).constFirst());
|
||||
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
|
||||
m_sslServer.setSslConfiguration(config);
|
||||
if (!m_sslServer.listen(QHostAddress::LocalHost, 1234) || !m_server.bind(&m_sslServer)) {
|
||||
qFatal() << "Server failed to listen on a port.";
|
||||
return;
|
||||
} else {
|
||||
qInfo() << "Server listening";
|
||||
qWarning() << "Server listening";
|
||||
}
|
||||
}
|
||||
|
||||
QString Server::createRoom(const QString &matrixId)
|
||||
{
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
.tags = {},
|
||||
};
|
||||
m_state += changes;
|
||||
auto roomId = generateRoomId();
|
||||
m_roomsToCreate += {roomId, matrixId};
|
||||
return roomId;
|
||||
}
|
||||
|
||||
void Server::inviteUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_invitedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
void Server::banUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.bans += Changes::BanUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_bannedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
void Server::joinUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.joins += Changes::JoinUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
}
|
||||
|
||||
QString Server::createServerNoticesRoom(const QString &matrixId)
|
||||
{
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
.tags = {u"m.server_notice"_s},
|
||||
};
|
||||
m_state += changes;
|
||||
return roomId;
|
||||
}
|
||||
|
||||
QString Server::sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content)
|
||||
{
|
||||
Changes changes;
|
||||
const auto eventId = generateEventId();
|
||||
changes.events += Changes::Event{
|
||||
.fullJson = QJsonObject{{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId}},
|
||||
};
|
||||
m_state += changes;
|
||||
return eventId;
|
||||
}
|
||||
|
||||
QString Server::sendStateEvent(const QString &roomId, const QString &eventType, const QString &stateKey, const QJsonObject &content)
|
||||
{
|
||||
Changes changes;
|
||||
const auto eventId = generateEventId();
|
||||
const auto json = QJsonObject{{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"state_key"_s, stateKey}};
|
||||
changes.events += Changes::Event{
|
||||
.fullJson = json,
|
||||
};
|
||||
changes.stateEvents += Changes::Event{.fullJson = json};
|
||||
m_state += changes;
|
||||
return eventId;
|
||||
}
|
||||
|
||||
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
|
||||
{
|
||||
QJsonObject joinRooms;
|
||||
auto token = request.query().queryItemValue(u"since"_s).toInt();
|
||||
|
||||
const auto changes = m_state.mid(token);
|
||||
for (const auto &change : changes) {
|
||||
for (const auto &newRoom : change.newRooms) {
|
||||
QJsonArray stateEvents;
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, newRoom.initialMembers[0]},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
for (const auto &member : newRoom.initialMembers) {
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, member},
|
||||
{u"state_key"_s, member},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
|
||||
auto room = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents}}}};
|
||||
|
||||
QJsonArray roomAccountData;
|
||||
QJsonObject tags;
|
||||
for (const auto &tag : newRoom.tags) {
|
||||
tags[tag] = QJsonObject();
|
||||
}
|
||||
if (!tags.empty()) {
|
||||
roomAccountData += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
|
||||
}
|
||||
|
||||
if (roomAccountData.size() > 0) {
|
||||
room[u"account_data"] = QJsonObject{{u"events"_s, roomAccountData}};
|
||||
}
|
||||
|
||||
joinRooms[newRoom.roomId] = room;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : changes) {
|
||||
for (const auto &invitation : change.invitations) {
|
||||
// TODO: The invitation could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[invitation.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, invitation.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, invitation.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(invitation.roomId)) {
|
||||
auto room = joinRooms[invitation.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[invitation.roomId] = room;
|
||||
} else {
|
||||
joinRooms[invitation.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : changes) {
|
||||
for (const auto &ban : change.bans) {
|
||||
// TODO: The ban could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[ban.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, ban.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, ban.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(ban.roomId)) {
|
||||
auto room = joinRooms[ban.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[ban.roomId] = room;
|
||||
} else {
|
||||
joinRooms[ban.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : changes) {
|
||||
for (const auto &join : change.joins) {
|
||||
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[join.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, join.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, join.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(join.roomId)) {
|
||||
auto room = joinRooms[join.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[join.roomId] = room;
|
||||
} else {
|
||||
joinRooms[join.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : changes) {
|
||||
for (const auto &state : change.stateEvents) {
|
||||
const auto &roomId = state.fullJson[u"room_id"_s].toString();
|
||||
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents.append(state.fullJson);
|
||||
auto room = joinRooms[roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[roomId] = room;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : changes) {
|
||||
for (const auto &event : change.events) {
|
||||
// TODO the room might be in a different join state.
|
||||
auto timeline = joinRooms[event.fullJson[u"room_id"_s].toString()][u"timeline"_s][u"events"_s].toArray();
|
||||
timeline += event.fullJson;
|
||||
if (joinRooms.contains(event.fullJson[u"room_id"_s].toString())) {
|
||||
auto room = joinRooms[event.fullJson[u"room_id"_s].toString()].toObject();
|
||||
room[u"timeline"_s] = QJsonObject{{u"events"_s, timeline}};
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = room;
|
||||
} else {
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = QJsonObject{
|
||||
{u"timeline"_s, QJsonObject{{u"events"_s, timeline}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject syncData = {
|
||||
// {u"account_data"_s, QJsonObject {}},
|
||||
// {u"presence"_s, QJsonObject {}},
|
||||
{u"next_batch"_s, QString::number(m_state.size())},
|
||||
};
|
||||
|
||||
QJsonObject rooms;
|
||||
if (!joinRooms.isEmpty()) {
|
||||
rooms[u"join"_s] = joinRooms;
|
||||
}
|
||||
|
||||
if (!rooms.empty()) {
|
||||
syncData[u"rooms"_s] = rooms;
|
||||
}
|
||||
|
||||
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
|
||||
m_joinedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
@@ -2,52 +2,10 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include <QHttpServer>
|
||||
#include <QJsonObject>
|
||||
#include <QSslServer>
|
||||
|
||||
struct Changes {
|
||||
struct NewRoom {
|
||||
QStringList initialMembers;
|
||||
QString roomId;
|
||||
QStringList tags;
|
||||
};
|
||||
QList<NewRoom> newRooms;
|
||||
|
||||
struct InviteUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<InviteUser> invitations;
|
||||
|
||||
struct BanUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<BanUser> bans;
|
||||
|
||||
struct JoinUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<JoinUser> joins;
|
||||
|
||||
struct Event {
|
||||
QJsonObject fullJson;
|
||||
};
|
||||
QList<Event> events;
|
||||
QList<Event> stateEvents;
|
||||
};
|
||||
|
||||
struct RoomData {
|
||||
QStringList members;
|
||||
QString id;
|
||||
QStringList tags;
|
||||
};
|
||||
|
||||
class Server : public QObject
|
||||
class Server
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Server();
|
||||
|
||||
@@ -63,18 +21,13 @@ public:
|
||||
void banUser(const QString &roomId, const QString &matrixId);
|
||||
void joinUser(const QString &roomId, const QString &matrixId);
|
||||
|
||||
/**
|
||||
* Create a server notices room.
|
||||
*/
|
||||
QString createServerNoticesRoom(const QString &matrixId);
|
||||
QString sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content);
|
||||
QString sendStateEvent(const QString &roomId, const QString &eventType, const QString &stateKey, const QJsonObject &content);
|
||||
|
||||
private:
|
||||
QHttpServer m_server;
|
||||
QSslServer m_sslServer;
|
||||
|
||||
void sync(const QHttpServerRequest &request, QHttpServerResponder &responder);
|
||||
QHash<QString, QList<QString>> m_invitedUsers;
|
||||
QHash<QString, QList<QString>> m_bannedUsers;
|
||||
QHash<QString, QList<QString>> m_joinedUsers;
|
||||
|
||||
QList<Changes> m_state;
|
||||
QList<std::pair<QString, QString>> m_roomsToCreate;
|
||||
};
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/eventstats.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
#include "server.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class ServerNoticesTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
NeoChatConnection *connection = nullptr;
|
||||
Server server;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void test();
|
||||
};
|
||||
|
||||
void ServerNoticesTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
QVERIFY(connection);
|
||||
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
RoomManager::instance().setConnection(connection);
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
}
|
||||
|
||||
void ServerNoticesTest::test()
|
||||
{
|
||||
auto roomTreeModel = RoomManager::instance().roomTreeModel();
|
||||
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 0);
|
||||
auto sortFilterRoomTreeModel = RoomManager::instance().sortFilterRoomTreeModel();
|
||||
const auto roomId = server.createServerNoticesRoom(u"@user:localhost:1234"_s);
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(connection->room(roomId)->isServerNoticeRoom());
|
||||
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 1);
|
||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
|
||||
server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"format"_s, u"org.matrix.custom.html"_s},
|
||||
{u"formatted_body"_s, u"Foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
sortFilterRoomTreeModel->invalidate();
|
||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 0);
|
||||
room->markAllMessagesAsRead();
|
||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ServerNoticesTest)
|
||||
#include "servernoticestest.moc"
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QTest>
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
@@ -33,7 +32,7 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
Q_UNUSED(testSyncFile.open(QIODevice::ReadOnly));
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
|
||||
update(std::move(roomData));
|
||||
@@ -47,7 +46,7 @@ inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFile
|
||||
if (!eventFileName.isEmpty()) {
|
||||
QFile testEventFile;
|
||||
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName);
|
||||
Q_UNUSED(testEventFile.open(QIODevice::ReadOnly));
|
||||
testEventFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
|
||||
return Quotient::loadEvent<EventT>(testSyncJson);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,6 @@ private Q_SLOTS:
|
||||
void stripDisallowedTags();
|
||||
void stripDisallowedAttributes();
|
||||
void emptyCodeTags();
|
||||
void addStyle_data();
|
||||
void addStyle();
|
||||
void dontAddStyle_data();
|
||||
void dontAddStyle();
|
||||
|
||||
void sendSimpleStringCase();
|
||||
void sendSingleParaMarkup();
|
||||
@@ -75,9 +71,6 @@ private Q_SLOTS:
|
||||
|
||||
void componentOutput_data();
|
||||
void componentOutput();
|
||||
|
||||
void updateSpoiler_data();
|
||||
void updateSpoiler();
|
||||
};
|
||||
|
||||
void TextHandlerTest::initTestCase()
|
||||
@@ -96,26 +89,21 @@ void TextHandlerTest::initTestCase()
|
||||
|
||||
void TextHandlerTest::allowedAttributes()
|
||||
{
|
||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
const QString testInputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
const QString testOutputString1S = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
const QString testOutputString1R = u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name());
|
||||
const QString testOutputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
// Handle urls where the href has either single (') or double (") quotes.
|
||||
const QString testInputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||
const QString testOutputString2S = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||
const QString testOutputString2R =
|
||||
u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a><a href='https://kde.org' style=\"text-decoration: none;\">link</a>"_s;
|
||||
const QString testOutputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString1);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString1S);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1R);
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString1);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1);
|
||||
|
||||
testTextHandler.setData(testInputString2);
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString2S);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2R);
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString2);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2);
|
||||
}
|
||||
|
||||
void TextHandlerTest::stripDisallowedTags()
|
||||
@@ -158,56 +146,6 @@ void TextHandlerTest::emptyCodeTags()
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::addStyle_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a>"_s;
|
||||
QTest::newRow("table")
|
||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
|
||||
<< u"<table style=\"width: 100%; border-collapse: collapse; border: 1px; border-style: solid;\"><tr><th style=\"border: 1px solid black; padding: 3px;\">Company</th><th style=\"border: 1px solid black; padding: 3px;\">Contact</th><th style=\"border: 1px solid black; padding: 3px;\">Country</th></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Alfreds Futterkiste</td><td style=\"border: 1px solid black; padding: 3px;\">Maria Anders</td><td style=\"border: 1px solid black; padding: 3px;\">Germany</td></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Centro comercial Moctezuma</td><td style=\"border: 1px solid black; padding: 3px;\">Francisco Chang</td><td style=\"border: 1px solid black; padding: 3px;\">Mexico</td></tr></table>"_s;
|
||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name());
|
||||
}
|
||||
|
||||
void TextHandlerTest::addStyle()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::dontAddStyle_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\">link</a>"_s;
|
||||
QTest::newRow("table")
|
||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
|
||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s;
|
||||
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
|
||||
<< u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
}
|
||||
|
||||
void TextHandlerTest::dontAddStyle()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendSimpleStringCase()
|
||||
{
|
||||
const QString testInputString = u"This data should just be left alone."_s;
|
||||
@@ -400,8 +338,7 @@ void TextHandlerTest::receiveRichInPlainOut()
|
||||
void TextHandlerTest::receivePlainTextIn()
|
||||
{
|
||||
const QString testInputString = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
||||
const QString testOutputStringRich =
|
||||
u"<plain text in tag bracket><br>Test link <a href=\"https://kde.org\" style=\"text-decoration: none;\">https://kde.org</a>."_s;
|
||||
const QString testOutputStringRich = u"<plain text in tag bracket><br>Test link <a href=\"https://kde.org\">https://kde.org</a>."_s;
|
||||
QString testOutputStringPlain = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
||||
|
||||
// Make sure quotes are maintained in a plain string.
|
||||
@@ -471,7 +408,7 @@ void TextHandlerTest::receivePlainStripMarkup()
|
||||
void TextHandlerTest::receiveRichUserPill()
|
||||
{
|
||||
const QString testInputString = u"<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>"_s;
|
||||
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\" style=\"text-decoration: none;\">@alice:example.org</a></b>"_s;
|
||||
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b>"_s;
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -523,23 +460,21 @@ void TextHandlerTest::receiveRichPlainUrl_data()
|
||||
// so we can confirm consistent behaviour for complex urls.
|
||||
QTest::addRow("link 1")
|
||||
<< u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>"_s
|
||||
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\" style=\"text-decoration: none;\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\" style=\"text-decoration: none;\">Link already rich</a>"_s;
|
||||
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>"_s;
|
||||
|
||||
// Another real case. The linkification wasn't handling it when a single link
|
||||
// contains what looks like and email. It was broken into 3 but needs to
|
||||
// be just single link.
|
||||
QTest::addRow("link 2")
|
||||
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
|
||||
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\" style=\"text-decoration: none;\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
|
||||
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
|
||||
|
||||
QTest::addRow("email")
|
||||
<< uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
|
||||
<< uR"(<a href="mailto:email@example.com" style="text-decoration: none;">email@example.com</a> <a href="mailto:email@example.com" style="text-decoration: none;">Link already rich</a>)"_s;
|
||||
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
|
||||
<< uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
|
||||
QTest::addRow("mxid")
|
||||
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
|
||||
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">Link already rich</a></b>"_s;
|
||||
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s
|
||||
<< u"a <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">@user:kde.org</a></b> b"_s;
|
||||
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
|
||||
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -661,35 +596,5 @@ void TextHandlerTest::componentOutput()
|
||||
QCOMPARE(testTextHandler.textComponents(testInputString), testOutputComponents);
|
||||
}
|
||||
|
||||
void TextHandlerTest::updateSpoiler_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
QTest::addColumn<bool>("spoilerRevealed");
|
||||
|
||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
QTest::newRow("same length") << u"<span data-mx-spoiler style=\"color: #123456; background: #123456;\">Test<span>"_s
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name())
|
||||
<< false;
|
||||
QTest::newRow("different length") << u"<span data-mx-spoiler style=\"color: short; background: looooooooooong;\">Test<span>"_s
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name())
|
||||
<< false;
|
||||
QTest::newRow("spoiler revealed")
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(theme->alternateBackgroundColor().name())
|
||||
<< u"<span data-mx-spoiler style=\"color: %1; background: %2;\">Test<span>"_s.arg(theme->textColor().name(), theme->alternateBackgroundColor().name())
|
||||
<< true;
|
||||
}
|
||||
|
||||
void TextHandlerTest::updateSpoiler()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
QFETCH(bool, spoilerRevealed);
|
||||
|
||||
QCOMPARE(TextHandler::updateSpoilerText(this, testInputString, spoilerRevealed), testOutputString);
|
||||
}
|
||||
|
||||
QTEST_MAIN(TextHandlerTest)
|
||||
#include "texthandlertest.moc"
|
||||
|
||||
@@ -161,7 +161,7 @@ void TimelineMessageModelTest::pendingEvent()
|
||||
// different every time.
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s);
|
||||
QVERIFY(testSyncFile.open(QIODevice::ReadOnly));
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
auto root = testSyncJson.object();
|
||||
auto timeline = root["timeline"_L1].toObject();
|
||||
@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
|
||||
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
||||
model->setRoom(room);
|
||||
|
||||
QCOMPARE(model->indexForEventId(u"$153456789:example.org"_s).row(), 0);
|
||||
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
|
||||
}
|
||||
|
||||
void TimelineMessageModelTest::cleanup()
|
||||
|
||||
@@ -73,16 +73,6 @@ void WindowControllerTest::toggle()
|
||||
instance.toggleWindow();
|
||||
QCOMPARE(window.windowState(), Qt::WindowNoState);
|
||||
QCOMPARE(window.isVisible(), false);
|
||||
|
||||
// make sure we restore maximized state when toggling
|
||||
instance.toggleWindow();
|
||||
window.setVisibility(QWindow::Maximized);
|
||||
QCOMPARE(window.windowState(), Qt::WindowMaximized);
|
||||
instance.toggleWindow();
|
||||
QCOMPARE(window.isVisible(), false);
|
||||
instance.toggleWindow();
|
||||
QCOMPARE(window.windowState(), Qt::WindowMaximized);
|
||||
QCOMPARE(window.isVisible(), true);
|
||||
}
|
||||
|
||||
QTEST_MAIN(WindowControllerTest)
|
||||
|
||||
14
cmake/Flatpak.cmake
Normal file
14
cmake/Flatpak.cmake
Normal file
@@ -0,0 +1,14 @@
|
||||
# SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Include FontConfig config which uses the Emoji One font from the
|
||||
# KDE Flatpak SDK.
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
|
||||
DESTINATION
|
||||
${CMAKE_INSTALL_SYSCONFDIR}/fonts/local.conf
|
||||
)
|
||||
|
||||
23
cmake/Flatpak/99-noto-mono-color-emoji.conf
Normal file
23
cmake/Flatpak/99-noto-mono-color-emoji.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<alias>
|
||||
<family>serif</family>
|
||||
<prefer>
|
||||
<family>Noto Color Emoji</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>sans-serif</family>
|
||||
<prefer>
|
||||
<family>Noto Color Emoji</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>monospace</family>
|
||||
<prefer>
|
||||
<family>Noto Color Emoji</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
</fontconfig>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
|
||||
qt_add_executable(timeline_memtest
|
||||
qt_add_executable(timeline-memtest
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(timeline_memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline_memtest PUBLIC
|
||||
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline-memtest PUBLIC
|
||||
Qt::Core
|
||||
Qt::Quick
|
||||
Qt::Qml
|
||||
@@ -16,13 +16,14 @@ target_link_libraries(timeline_memtest PUBLIC
|
||||
Qt::QuickControls2
|
||||
Qt::Widgets
|
||||
KF6::I18nQml
|
||||
KF6::Kirigami
|
||||
QuotientQt6
|
||||
LibNeoChat
|
||||
Timeline
|
||||
)
|
||||
|
||||
ecm_add_qml_module(timeline_memtest URI org.kde.neochat.timeline_memtest GENERATE_PLUGIN_SOURCE
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline_memtest
|
||||
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest
|
||||
QML_FILES
|
||||
Main.qml
|
||||
SOURCES
|
||||
|
||||
@@ -28,7 +28,7 @@ int main(int argc, char **argv)
|
||||
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
||||
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
||||
|
||||
engine.loadFromModule("org.kde.neochat.timeline_memtest", "Main");
|
||||
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,7 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
auto ok = testSyncFile.open(QIODevice::ReadOnly);
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << testSyncFile.fileName() << testSyncFile.errorString();
|
||||
}
|
||||
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
||||
auto timelineJson = testSyncJson["timeline"_L1].toObject();
|
||||
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,7 @@ Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[tr]=NeoChat
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Name[zh_TW]=NeoChat
|
||||
GenericName=Matrix Client
|
||||
@@ -87,6 +88,7 @@ GenericName[sv]=Matrix-klient
|
||||
GenericName[ta]=Matrix வாங்கி
|
||||
GenericName[tr]=Matrix İstemcisi
|
||||
GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
GenericName[zh_TW]=Matrix 用戶端
|
||||
Comment=Chat on Matrix
|
||||
@@ -108,12 +110,10 @@ Comment[ia]=Conversation en ditecto sur Matrix
|
||||
Comment[it]= su Matrix
|
||||
Comment[ka]=ჩატი Matrix-ზე
|
||||
Comment[ko]=Matrix에서 대화하기
|
||||
Comment[lt]=Pokalbiai per Matrix
|
||||
Comment[lv]=Tērzējiet „Matrix“ tīklā
|
||||
Comment[nl]=Chat op Matrix
|
||||
Comment[pl]=Rozmawiaj na Matriksie
|
||||
Comment[pt_BR]=Bate papo na Matrix
|
||||
Comment[ro]=Discutați pe Matrix
|
||||
Comment[ru]=Общение в Matrix
|
||||
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
|
||||
Comment[sl]=Klepet na Matrixu
|
||||
@@ -121,6 +121,7 @@ Comment[sv]=Chatta på Matrix
|
||||
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
|
||||
Comment[tr]=Matrix üzerinde sohbet edin
|
||||
Comment[uk]=Спілкування у Matrix
|
||||
Comment[x-test]=xxChat on Matrixxx
|
||||
Comment[zh_CN]=在 Matrix 上聊天
|
||||
Comment[zh_TW]=在 Matrix 上聊天
|
||||
MimeType=x-scheme-handler/matrix;
|
||||
|
||||
5249
po/ar/neochat.po
5249
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
4393
po/ast/neochat.po
4393
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
5212
po/az/neochat.po
5212
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
5053
po/ca/neochat.po
5053
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4909
po/cs/neochat.po
4909
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
5126
po/da/neochat.po
5126
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
5743
po/de/neochat.po
5743
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
5428
po/el/neochat.po
5428
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
5770
po/en_GB/neochat.po
5770
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
5614
po/eo/neochat.po
5614
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
4649
po/es/neochat.po
4649
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
5435
po/eu/neochat.po
5435
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
5394
po/fi/neochat.po
5394
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
5269
po/fr/neochat.po
5269
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
7580
po/ga/neochat.po
7580
po/ga/neochat.po
File diff suppressed because it is too large
Load Diff
5810
po/gl/neochat.po
5810
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
5027
po/he/neochat.po
5027
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
5643
po/hi/neochat.po
5643
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
5857
po/hu/neochat.po
5857
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
5166
po/ia/neochat.po
5166
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
5417
po/id/neochat.po
5417
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
5202
po/ie/neochat.po
5202
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
5129
po/it/neochat.po
5129
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
4390
po/ja/neochat.po
4390
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
5091
po/ka/neochat.po
5091
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
5529
po/ko/neochat.po
5529
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
5673
po/lt/neochat.po
5673
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
5438
po/lv/neochat.po
5438
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
5097
po/nl/neochat.po
5097
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
5083
po/nn/neochat.po
5083
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
5195
po/pa/neochat.po
5195
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
5453
po/pl/neochat.po
5453
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
5383
po/pt/neochat.po
5383
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -1,122 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
|
||||
<!ENTITY % Brazilian-Portuguese "INCLUDE">
|
||||
]>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
-->
|
||||
|
||||
<refentry lang="&language;">
|
||||
<refentryinfo>
|
||||
<title
|
||||
>Manual do Usuário do NeoChat</title>
|
||||
<author
|
||||
><firstname
|
||||
>Carl</firstname
|
||||
><surname
|
||||
>Schwan</surname
|
||||
> <contrib
|
||||
>NeoChat man page.</contrib
|
||||
> <email
|
||||
>carl@carlschwan.eu</email
|
||||
></author>
|
||||
<date
|
||||
>01/11/2022</date>
|
||||
<releaseinfo
|
||||
>22.09</releaseinfo>
|
||||
<productname
|
||||
>NeoChat</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>
|
||||
<command
|
||||
>neochat</command>
|
||||
</refentrytitle>
|
||||
<manvolnum
|
||||
>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname
|
||||
>neochat</refname>
|
||||
<refpurpose
|
||||
>Cliente para interação com o protocolo de mensagens Matrix.</refpurpose>
|
||||
</refnamediv>
|
||||
<!-- body begins here -->
|
||||
<refsynopsisdiv id='synopsis'>
|
||||
<cmdsynopsis
|
||||
><command
|
||||
>neochat</command
|
||||
> <arg choice="opt"
|
||||
><replaceable
|
||||
>URI</replaceable
|
||||
></arg
|
||||
> </cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
|
||||
<refsect1 id="description">
|
||||
<title
|
||||
>Descrição</title>
|
||||
<para
|
||||
>O <command
|
||||
>neochat</command
|
||||
> é um aplicativo de bate-papo para o protocolo Matrix. Ele funciona tanto em computadores quanto em dispositivos móveis. </para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="options"
|
||||
><title
|
||||
>Opções</title>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term
|
||||
><option
|
||||
>URI</option
|
||||
></term>
|
||||
<listitem>
|
||||
<para
|
||||
>O URI da matriz para um usuário ou uma sala. Por exemplo, matrix:u/usuário:exemplo.org e matrix:r/root:exemplo.org. Isso fará com que o NeoChat tente abrir a sala ou conversa especificada. </para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="bug">
|
||||
<title
|
||||
>Relatar bugs</title>
|
||||
<para
|
||||
>Você pode reportar erros e solicitar novas funcionalidades em <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General"
|
||||
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General</ulink
|
||||
></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title
|
||||
>Veja também</title>
|
||||
<simplelist>
|
||||
<member
|
||||
>Lista de perguntas frequentes sobre o Matrix <ulink url="https://matrix.org/faq/"
|
||||
>https://matrix.org/faq/</ulink
|
||||
> </member>
|
||||
<member
|
||||
>kf5options(7)</member>
|
||||
<member
|
||||
>qt5options(7)</member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="copyright"
|
||||
><title
|
||||
>Direitos autorais</title>
|
||||
<para
|
||||
>Direitos autorais © 2020-2022 Tobias Fella </para>
|
||||
<para
|
||||
>Direitos autorais © 2020-2022 Carl Schwan </para>
|
||||
<para
|
||||
>Licença: GNU General Public Versão 3 ou posterior <ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||
>></para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
8259
po/pt_BR/neochat.po
8259
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
7961
po/ro/neochat.po
7961
po/ro/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -1,122 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
|
||||
<!ENTITY % Russian "INCLUDE">
|
||||
]>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
-->
|
||||
|
||||
<refentry lang="&language;">
|
||||
<refentryinfo>
|
||||
<title
|
||||
>Руководство пользователя NeoChat</title>
|
||||
<author
|
||||
><firstname
|
||||
>Carl</firstname
|
||||
><surname
|
||||
>Schwan</surname
|
||||
> <contrib
|
||||
>man-страница NeoChat.</contrib
|
||||
> <email
|
||||
>carl@carlschwan.eu</email
|
||||
></author>
|
||||
<date
|
||||
>2022-11-01</date>
|
||||
<releaseinfo
|
||||
>22.09</releaseinfo>
|
||||
<productname
|
||||
>NeoChat</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>
|
||||
<command
|
||||
>neochat</command>
|
||||
</refentrytitle>
|
||||
<manvolnum
|
||||
>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname
|
||||
>neochat</refname>
|
||||
<refpurpose
|
||||
>Клиент для взаимодействия с протоколом обмена сообщениями Matrix</refpurpose>
|
||||
</refnamediv>
|
||||
<!-- body begins here -->
|
||||
<refsynopsisdiv id='synopsis'>
|
||||
<cmdsynopsis
|
||||
><command
|
||||
>neochat</command
|
||||
> <arg choice="opt"
|
||||
><replaceable
|
||||
>URI</replaceable
|
||||
></arg
|
||||
> </cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
|
||||
<refsect1 id="description">
|
||||
<title
|
||||
>Описание</title>
|
||||
<para
|
||||
><command
|
||||
>neochat</command
|
||||
> — приложение для настольных и мобильных устройств, позволяющее общаться в чатах с помощью протокола Matrix. </para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="options"
|
||||
><title
|
||||
>Параметры</title>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term
|
||||
><option
|
||||
>URI</option
|
||||
></term>
|
||||
<listitem>
|
||||
<para
|
||||
>URI-адрес пользователя или комнаты в Matrix, например: matrix:u/user:example.org и matrix:r/root:example.org. NeoChat попытается открыть указанную комнату или беседу. </para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="bug">
|
||||
<title
|
||||
>Отчёты об ошибках</title>
|
||||
<para
|
||||
>Сообщать об ошибках и отправлять предложения по улучшению можно по адресу <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General"
|
||||
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General</ulink
|
||||
></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title
|
||||
>Смотрите также</title>
|
||||
<simplelist>
|
||||
<member
|
||||
>Список наиболее часто задаваемых вопросов о Matrix <ulink url="https://matrix.org/faq/"
|
||||
>https://matrix.org/faq/</ulink
|
||||
> </member>
|
||||
<member
|
||||
>kf5options(7)</member>
|
||||
<member
|
||||
>qt5options(7)</member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="copyright"
|
||||
><title
|
||||
>Авторские права</title>
|
||||
<para
|
||||
>Авторские права © Tobias Fella, 2020–2022 </para>
|
||||
<para
|
||||
>Авторские права © Carl Schwan, 2020–2022 </para>
|
||||
<para
|
||||
>Лицензия: стандартная общественная лицензия GNU версии 3 или любой более поздней версии <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||
>></para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
5469
po/ru/neochat.po
5469
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
5645
po/sa/neochat.po
5645
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
5285
po/sk/neochat.po
5285
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
5095
po/sl/neochat.po
5095
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
5478
po/sv/neochat.po
5478
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
5603
po/ta/neochat.po
5603
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
5008
po/tok/neochat.po
5008
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
>carl@carlschwan.eu</email
|
||||
></author>
|
||||
<date
|
||||
>2022‒11‒01</date>
|
||||
>2022-11-01</date>
|
||||
<releaseinfo
|
||||
>22.09</releaseinfo>
|
||||
<productname
|
||||
@@ -111,9 +111,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
><title
|
||||
>Telif Hakkı</title>
|
||||
<para
|
||||
>Telif hakkı © 2020–2022 Tobias Fella </para>
|
||||
>Telif hakkı © 2020-2022 Tobias Fella </para>
|
||||
<para
|
||||
>Telif hakkı © 2020–2022 Carl Schwan </para>
|
||||
>Telif hakkı © 2020-2022 Carl Schwan </para>
|
||||
<para
|
||||
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||
|
||||
5087
po/tr/neochat.po
5087
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
5112
po/uk/neochat.po
5112
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
4596
po/zh_CN/neochat.po
4596
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
5209
po/zh_TW/neochat.po
5209
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
qt_add_library(neochat STATIC
|
||||
add_library(neochat STATIC
|
||||
controller.cpp
|
||||
controller.h
|
||||
roommanager.cpp
|
||||
@@ -35,10 +35,6 @@ qt_add_library(neochat STATIC
|
||||
models/commonroomsmodel.h
|
||||
texttospeechhelper.h
|
||||
texttospeechhelper.cpp
|
||||
models/limitermodel.cpp
|
||||
models/limitermodel.h
|
||||
supportcontroller.cpp
|
||||
supportcontroller.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
@@ -52,8 +48,6 @@ if(ANDROID OR WIN32)
|
||||
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
|
||||
QT_QML_SOURCE_TYPENAME GlobalMenu
|
||||
)
|
||||
else()
|
||||
set(EXTRA_IMPORTS org.kde.purpose)
|
||||
endif()
|
||||
|
||||
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
@@ -70,6 +64,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/AttachmentPane.qml
|
||||
qml/QuickFormatBar.qml
|
||||
qml/UserDetailDialog.qml
|
||||
qml/OpenFileDialog.qml
|
||||
qml/KeyVerificationDialog.qml
|
||||
qml/ConfirmLogoutDialog.qml
|
||||
qml/VerificationMessage.qml
|
||||
@@ -78,6 +73,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/EmojiSas.qml
|
||||
qml/VerificationCanceled.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/InvitationView.qml
|
||||
qml/AvatarTabButton.qml
|
||||
qml/OsmLocationPlugin.qml
|
||||
@@ -103,14 +99,11 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/HoverLinkIndicator.qml
|
||||
qml/AvatarNotification.qml
|
||||
qml/ReasonDialog.qml
|
||||
qml/NewPollDialog.qml
|
||||
qml/UserMenu.qml
|
||||
qml/MeetingDialog.qml
|
||||
qml/SeenByDialog.qml
|
||||
qml/SupportDialog.qml
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
io.github.quotient_im.libquotient
|
||||
IMPORTS
|
||||
org.kde.neochat.libneochat
|
||||
org.kde.neochat.rooms
|
||||
@@ -122,15 +115,13 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
org.kde.neochat.devtools
|
||||
org.kde.neochat.login
|
||||
org.kde.neochat.chatbar
|
||||
org.kde.config
|
||||
org.kde.syntaxhighlighting
|
||||
${EXTRA_IMPORTS}
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
qt_target_qml_sources(neochat QML_FILES
|
||||
qml/ShareAction.qml
|
||||
qml/GlobalMenu.qml
|
||||
qml/EditMenu.qml
|
||||
)
|
||||
else()
|
||||
qt_target_qml_sources(neochat QML_FILES
|
||||
@@ -143,17 +134,10 @@ if(WIN32)
|
||||
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
|
||||
endif()
|
||||
|
||||
qt_add_executable(neochat-app
|
||||
add_executable(neochat-app
|
||||
main.cpp
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
set_target_properties(neochat-app PROPERTIES
|
||||
OUTPUT_NAME "neochat-app"
|
||||
PREFIX "lib"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(TARGET Qt::WebView)
|
||||
target_link_libraries(neochat-app PUBLIC Qt::WebView)
|
||||
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
|
||||
@@ -163,7 +147,6 @@ target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
|
||||
|
||||
target_link_libraries(neochat-app PRIVATE
|
||||
neochat
|
||||
KF6::IconThemes
|
||||
)
|
||||
|
||||
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
||||
@@ -171,7 +154,12 @@ ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
||||
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
|
||||
|
||||
if(NOT ANDROID)
|
||||
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
|
||||
if (NOT WIN32 AND NOT APPLE)
|
||||
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
|
||||
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
|
||||
else()
|
||||
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
|
||||
endif()
|
||||
target_link_libraries(neochat PUBLIC KF6::WindowSystem)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
|
||||
endif()
|
||||
@@ -189,7 +177,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
||||
target_link_libraries(neochat PRIVATE neochatplugin Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
||||
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
||||
target_link_libraries(neochat PUBLIC
|
||||
LibNeoChat
|
||||
Timeline
|
||||
@@ -209,9 +197,8 @@ target_link_libraries(neochat PUBLIC
|
||||
KF6::ConfigGui
|
||||
KF6::CoreAddons
|
||||
KF6::SonnetCore
|
||||
KF6::IconThemes
|
||||
KF6::ItemModels
|
||||
KF6::I18nQml
|
||||
KirigamiApp
|
||||
QuotientQt6
|
||||
Login
|
||||
Rooms
|
||||
@@ -219,6 +206,10 @@ target_link_libraries(neochat PUBLIC
|
||||
Spaces
|
||||
)
|
||||
|
||||
if (TARGET KF6::Crash)
|
||||
target_link_libraries(neochat PUBLIC KF6::Crash)
|
||||
endif()
|
||||
|
||||
kconfig_target_kcfg_file(neochat FILE neochatconfig.kcfg CLASS_NAME NeoChatConfig MUTATORS GENERATE_PROPERTIES DEFAULT_VALUE_GETTERS PARENT_IN_CONSTRUCTOR SINGLETON GENERATE_MOC QML_REGISTRATION)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
@@ -329,7 +320,6 @@ if(ANDROID)
|
||||
"kt-restore-defaults-symbolic"
|
||||
"user-symbolic"
|
||||
"mark-location-symbolic"
|
||||
"amarok_playcount"
|
||||
|
||||
${KIRIGAMI_ADDONS_ICONS}
|
||||
)
|
||||
@@ -348,7 +338,14 @@ if(TARGET KF6::DBusAddons AND NOT WIN32)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
|
||||
endif()
|
||||
|
||||
if (TARGET KF6::KIOWidgets)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
|
||||
endif()
|
||||
|
||||
if (TARGET KUnifiedPush)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
|
||||
target_link_libraries(neochat PUBLIC KUnifiedPush)
|
||||
|
||||
if (NOT ANDROID)
|
||||
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
|
||||
@@ -358,8 +355,7 @@ endif()
|
||||
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
# krunner plugin must be the same as the app id for flatpak to export it
|
||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins RENAME org.kde.neochat.desktop)
|
||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "controller.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <qt6keychain/keychain.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -13,24 +14,38 @@
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/settings.h>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "enums/roomsortparameter.h"
|
||||
#include "general_logging.h"
|
||||
#include "mediasizehelper.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/messagemodel.h"
|
||||
#include "models/pushrulemodel.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "models/roomtreemodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "proxycontroller.h"
|
||||
#include "roommanager.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
#include "trayicon.h"
|
||||
#elif !defined(Q_OS_ANDROID)
|
||||
#include "trayicon_sni.h"
|
||||
#endif
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
@@ -121,10 +136,8 @@ Controller::Controller(QObject *parent)
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||
#endif
|
||||
|
||||
connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
#ifndef Q_OS_ANDROID
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
delete m_trayIcon;
|
||||
#endif
|
||||
NeoChatConfig::self()->save();
|
||||
});
|
||||
|
||||
@@ -190,15 +203,13 @@ void Controller::setAccountManager(AccountManager *manager)
|
||||
|
||||
m_accountManager = manager;
|
||||
|
||||
if (!m_accountManager) {
|
||||
return;
|
||||
if (m_accountManager) {
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
}
|
||||
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
}
|
||||
|
||||
void Controller::initConnection(NeoChatConnection *connection)
|
||||
@@ -244,10 +255,7 @@ void Controller::initActiveConnection(NeoChatConnection *oldConnection, NeoChatC
|
||||
if (newConnection) {
|
||||
connect(newConnection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
|
||||
connect(newConnection, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
|
||||
|
||||
// Refresh and update manually, in case we init too late for the badge count to actually change.
|
||||
newConnection->refreshBadgeNotificationCount();
|
||||
updateBadgeNotificationCount(newConnection->badgeNotificationCount());
|
||||
}
|
||||
Q_EMIT activeConnectionChanged(newConnection);
|
||||
}
|
||||
@@ -257,8 +265,8 @@ bool Controller::supportSystemTray() const
|
||||
#ifdef Q_OS_ANDROID
|
||||
return false;
|
||||
#else
|
||||
QStringList unsupportedPlatforms{u"GNOME"_s, u"Pantheon"_s};
|
||||
return !unsupportedPlatforms.contains(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP")));
|
||||
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
|
||||
return de != u"GNOME"_s && de != u"Pantheon"_s;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -268,8 +276,11 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
} else if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
} else {
|
||||
if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -307,7 +318,8 @@ void Controller::listenForNotifications()
|
||||
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
|
||||
|
||||
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
|
||||
NotificationsManager::postPushNotification(data);
|
||||
instance().m_notificationsManager.postPushNotification(data);
|
||||
timer->stop();
|
||||
});
|
||||
|
||||
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
|
||||
@@ -325,7 +337,30 @@ void Controller::clearInvitationNotification(const QString &roomId)
|
||||
|
||||
void Controller::updateBadgeNotificationCount(int count)
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
// copied from Telegram desktop
|
||||
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
|
||||
// Gnome requires that count is a 64bit integer
|
||||
const qint64 counterSlice = std::min(count, 9999);
|
||||
QVariantMap dbusUnityProperties;
|
||||
|
||||
if (counterSlice > 0) {
|
||||
dbusUnityProperties["count"_L1] = counterSlice;
|
||||
dbusUnityProperties["count-visible"_L1] = true;
|
||||
} else {
|
||||
dbusUnityProperties["count-visible"_L1] = false;
|
||||
}
|
||||
|
||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
|
||||
|
||||
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||
|
||||
QDBusConnection::sessionBus().send(signal);
|
||||
#endif // Q_OS_ANDROID
|
||||
#else
|
||||
qGuiApp->setBadgeNumber(count);
|
||||
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||
}
|
||||
|
||||
bool Controller::isFlatpak() const
|
||||
@@ -346,10 +381,7 @@ QString Controller::loadFileContent(const QString &path) const
|
||||
{
|
||||
QUrl url(path);
|
||||
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qCWarning(GENERAL) << "Failed to open file" << path;
|
||||
return {};
|
||||
}
|
||||
file.open(QFile::ReadOnly);
|
||||
return QString::fromLatin1(file.readAll());
|
||||
}
|
||||
|
||||
|
||||
@@ -110,9 +110,8 @@ private:
|
||||
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QPointer<TrayIcon> m_trayIcon;
|
||||
#endif
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
QString m_endpoint;
|
||||
QStringList m_shownImages;
|
||||
|
||||
|
||||
@@ -33,10 +33,13 @@
|
||||
#include <KWindowSystem>
|
||||
#endif
|
||||
|
||||
#if __has_include("KCrash")
|
||||
#include <KCrash>
|
||||
#endif
|
||||
|
||||
#include <KIconTheme>
|
||||
#include <KLocalizedQmlContext>
|
||||
#include <KLocalizedContext>
|
||||
#include <KLocalizedString>
|
||||
#include <KirigamiApp>
|
||||
|
||||
#include "neochat-version.h"
|
||||
|
||||
@@ -101,11 +104,8 @@ Q_DECL_EXPORT
|
||||
#endif
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
|
||||
// We currently need to do this ourselves,
|
||||
// KirigamiApp currently called this after constructing the app which breaks icons on Windows.
|
||||
KIconTheme::initTheme();
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
QtWebView::initialize();
|
||||
@@ -113,10 +113,24 @@ int main(int argc, char *argv[])
|
||||
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
|
||||
#endif
|
||||
|
||||
KirigamiApp::App app(argc, argv);
|
||||
KirigamiApp kirigamiApp;
|
||||
#ifdef Q_OS_ANDROID
|
||||
QGuiApplication app(argc, argv);
|
||||
QQuickStyle::setStyle(u"org.kde.breeze"_s);
|
||||
#else
|
||||
QIcon::setFallbackThemeName("breeze"_L1);
|
||||
QApplication app(argc, argv);
|
||||
// Default to org.kde.desktop style unless the user forces another style
|
||||
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
|
||||
QQuickStyle::setStyle(u"org.kde.desktop"_s);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
}
|
||||
|
||||
QApplication::setStyle(u"breeze"_s);
|
||||
QFont font(u"Segoe UI Emoji"_s);
|
||||
font.setPointSize(10);
|
||||
@@ -163,9 +177,19 @@ int main(int argc, char *argv[])
|
||||
KAboutData::setApplicationData(about);
|
||||
QGuiApplication::setWindowIcon(QIcon::fromTheme(u"org.kde.neochat"_s));
|
||||
|
||||
#if __has_include("KCrash")
|
||||
KCrash::initialize();
|
||||
#endif
|
||||
|
||||
Connection::setEncryptionDefault(true);
|
||||
Connection::setDirectChatEncryptionDefault(true);
|
||||
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
// Copy over the included FontConfig configuration to the
|
||||
// app's config dir:
|
||||
QFile::copy(u"/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf"_s, u"/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"_s);
|
||||
#endif
|
||||
|
||||
ColorSchemer colorScheme;
|
||||
|
||||
QCommandLineParser parser;
|
||||
@@ -181,7 +205,7 @@ int main(int argc, char *argv[])
|
||||
parser.addOption(testOption);
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s);
|
||||
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s, i18n("Internal usage only."));
|
||||
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
|
||||
parser.addOption(dbusActivatedOption);
|
||||
#endif
|
||||
@@ -195,14 +219,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
if (parser.isSet(dbusActivatedOption)) {
|
||||
#ifdef HAVE_KDBUSADDONS
|
||||
// We *don't* want to use KDBusService here. I don't know why, but it makes activation super unreliable. We don't really need it anyway.
|
||||
if (!QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.neochat"))) {
|
||||
// Gracefully fail if NeoChat is already running
|
||||
qWarning() << "NeoChat already running, not sending push notifications.";
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
// We want to be replaceable by the main client
|
||||
KDBusService service(KDBusService::Replace);
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
// If we are built with KRunner and KUnifiedPush support, we need to do something special.
|
||||
@@ -261,7 +279,7 @@ int main(int argc, char *argv[])
|
||||
});
|
||||
#endif
|
||||
|
||||
KLocalization::setupLocalizedContext(&engine);
|
||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
||||
|
||||
if (parser.isSet("ignore-ssl-errors"_L1)) {
|
||||
@@ -276,9 +294,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
engine.addImageProvider(u"blurhash"_s, new BlurhashImageProvider);
|
||||
|
||||
if (!kirigamiApp.start("org.kde.neochat", "Main", &engine)) {
|
||||
return -1;
|
||||
}
|
||||
engine.loadFromModule("org.kde.neochat", "Main");
|
||||
|
||||
if (!parser.positionalArguments().isEmpty() && !parser.isSet("share"_L1)) {
|
||||
RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "jobs/neochatgetcommonroomsjob.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -40,22 +39,8 @@ void CommonRoomsModel::setUserId(const QString &userId)
|
||||
|
||||
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
|
||||
{
|
||||
auto roomId = m_commonRooms[index.row()];
|
||||
auto room = connection()->room(roomId);
|
||||
if (!room) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (roleName) {
|
||||
case Qt::DisplayRole:
|
||||
case RoomNameRole:
|
||||
return room->displayName();
|
||||
case RoomAvatarRole:
|
||||
return room->avatarUrl();
|
||||
case RoomIdRole:
|
||||
return roomId;
|
||||
}
|
||||
|
||||
Q_UNUSED(index)
|
||||
Q_UNUSED(roleName)
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -65,20 +50,6 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const
|
||||
return m_commonRooms.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CommonRoomsModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{RoomIdRole, "roomId"},
|
||||
{RoomNameRole, "roomName"},
|
||||
{RoomAvatarRole, "roomAvatar"},
|
||||
};
|
||||
}
|
||||
|
||||
bool CommonRoomsModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void CommonRoomsModel::reload()
|
||||
{
|
||||
if (!m_connection || m_userId.isEmpty()) {
|
||||
@@ -94,26 +65,15 @@ void CommonRoomsModel::reload()
|
||||
return;
|
||||
}
|
||||
|
||||
m_loading = true;
|
||||
Q_EMIT loadingChanged();
|
||||
|
||||
m_connection->callApi<NeochatGetCommonRoomsJob>(m_userId)
|
||||
.then([this](const auto job) {
|
||||
const auto &replyData = job->jsonData();
|
||||
beginResetModel();
|
||||
for (const auto &roomId : replyData[u"joined"_s].toArray()) {
|
||||
m_commonRooms.push_back(roomId.toString());
|
||||
}
|
||||
endResetModel();
|
||||
Q_EMIT countChanged();
|
||||
|
||||
m_loading = false;
|
||||
Q_EMIT loadingChanged();
|
||||
})
|
||||
.onFailure([this] {
|
||||
m_loading = false;
|
||||
Q_EMIT loadingChanged();
|
||||
});
|
||||
m_connection->callApi<NeochatGetCommonRoomsJob>(m_userId).then([this](const auto job) {
|
||||
const auto &replyData = job->jsonData();
|
||||
beginResetModel();
|
||||
for (const auto &roomId : replyData[u"joined"_s].toArray()) {
|
||||
m_commonRooms.push_back(roomId.toString());
|
||||
}
|
||||
endResetModel();
|
||||
Q_EMIT countChanged();
|
||||
});
|
||||
}
|
||||
|
||||
#include "moc_commonroomsmodel.cpp"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/roommember.h>
|
||||
@@ -21,13 +22,14 @@ class CommonRoomsModel : public QAbstractListModel
|
||||
Q_PROPERTY(NeoChatConnection *connection WRITE setConnection READ connection NOTIFY connectionChanged REQUIRED)
|
||||
Q_PROPERTY(QString userId WRITE setUserId READ userId NOTIFY userIdChanged REQUIRED)
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
RoomIdRole = Qt::UserRole,
|
||||
RoomNameRole,
|
||||
RoomAvatarRole,
|
||||
TextRole = Qt::DisplayRole,
|
||||
LongitudeRole,
|
||||
LatitudeRole,
|
||||
AssetRole,
|
||||
AuthorRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
@@ -42,15 +44,10 @@ public:
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
||||
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
bool loading() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void userIdChanged();
|
||||
void countChanged();
|
||||
void loadingChanged();
|
||||
|
||||
private:
|
||||
void reload();
|
||||
@@ -58,5 +55,4 @@ private:
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
QString m_userId;
|
||||
QList<QString> m_commonRooms;
|
||||
bool m_loading = false;
|
||||
};
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "models/limitermodel.h"
|
||||
|
||||
LimiterModel::LimiterModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged);
|
||||
connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged);
|
||||
connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged);
|
||||
}
|
||||
|
||||
int LimiterModel::maximumCount() const
|
||||
{
|
||||
return m_maximumCount;
|
||||
}
|
||||
|
||||
void LimiterModel::setMaximumCount(int maximumCount)
|
||||
{
|
||||
if (m_maximumCount != maximumCount) {
|
||||
m_maximumCount = maximumCount;
|
||||
Q_EMIT maximumCountChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int LimiterModel::extraCount() const
|
||||
{
|
||||
if (sourceModel()) {
|
||||
return std::max(sourceModel()->rowCount() - maximumCount(), 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent)
|
||||
return source_row < maximumCount();
|
||||
}
|
||||
|
||||
#include "moc_limitermodel.cpp"
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class LimiterModel
|
||||
*
|
||||
* @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount.
|
||||
*
|
||||
* Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch.
|
||||
*/
|
||||
class LimiterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
|
||||
Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged)
|
||||
|
||||
public:
|
||||
explicit LimiterModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] int maximumCount() const;
|
||||
void setMaximumCount(int maximumCount);
|
||||
|
||||
[[nodiscard]] int extraCount() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void maximumCountChanged();
|
||||
void extraCountChanged();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
private:
|
||||
int m_maximumCount = 0;
|
||||
};
|
||||
@@ -58,7 +58,7 @@ QVariant NotificationsModel::data(const QModelIndex &index, int role) const
|
||||
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{TextRole, "notificationText"},
|
||||
{TextRole, "text"},
|
||||
{RoomIdRole, "roomId"},
|
||||
{AuthorName, "authorName"},
|
||||
{AuthorAvatar, "authorAvatar"},
|
||||
@@ -92,9 +92,7 @@ void NotificationsModel::setConnection(NeoChatConnection *connection)
|
||||
|
||||
void NotificationsModel::loadData()
|
||||
{
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(m_connection);
|
||||
if (m_job || (m_notifications.size() && m_nextToken.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ void ServerListModel::initialize()
|
||||
true,
|
||||
false,
|
||||
});
|
||||
endResetModel();
|
||||
beginResetModel();
|
||||
}
|
||||
|
||||
#include "moc_serverlistmodel.cpp"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user