Page MenuHomePhorge

No OneTemporary

Size
55 KB
Referenced Files
None
Subscribers
None
diff --git a/src/contents/ui/Bubble.qml b/src/contents/ui/Bubble.qml
index 8709a56..fe77df3 100644
--- a/src/contents/ui/Bubble.qml
+++ b/src/contents/ui/Bubble.qml
@@ -1,270 +1,270 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import org.kde.kirigami 2.13 as Kirigami
import '.' as Kazv
import 'matrix-helpers.js' as Helpers
ItemDelegate {
id: upper
objectName: 'bubble'
default property var children
property var currentEvent: event
property var menuContent: []
property var shouldDisplayTime: displayTime || (!compactMode && !event.isLocalEcho)
- property var eventPinned: event.eventId && pinnedEvents.eventIds.includes(event.eventId)
+ property var eventPinned: event.eventId && pinnedEvents.eventIds.hasOwnProperty(event.eventId)
readonly property var eventReactions: event.reactions()
topPadding: 0
bottomPadding: 0
readonly property var bubbleSpacing: leftPadding + rightPadding
readonly property double replyMaxHeight: Kirigami.Units.gridUnit * 7
Layout.fillWidth: true
property var backgroundItem: Rectangle {
anchors.fill: parent
color: (hovered || isSelected) ? Kirigami.Theme.activeBackgroundColor : Kirigami.Theme.backgroundColor
}
background: compactMode ? null : backgroundItem
property var eventSourcePopupComp: Component {
Kazv.SelfDestroyableOverlaySheet {
shouldSelfDestroy: true
title: l10n.get('event-source-popup-title')
Kazv.EventSourceView {
Layout.preferredWidth: Math.min(Kirigami.Units.gridUnit * 40, Window.width)
event: upper.currentEvent
}
}
}
property var reactionPopupComp: Component {
Kazv.ReactToEventPopup {
shouldSelfDestroy: true
onAccepted: eventView.reactWith(text)
}
}
function getIsEditable(event) {
return event.sender === matrixSdk.userId
&& event.type === 'm.room.message'
&& event.content.msgtype === 'm.text';
}
property var menuComp: Component {
Menu {
objectName: 'bubbleContextMenu'
property list<QtObject> staticItems: [
Kirigami.Action {
objectName: 'replyToMenuItem'
text: l10n.get('event-reply-action')
onTriggered: setDraftRelation('m.in_reply_to', currentEvent.eventId)
enabled: event && !event.redacted
},
Kirigami.Action {
objectName: 'editMenuItem'
text: l10n.get('event-edit-action')
onTriggered: {
setDraftRelation('m.replace', currentEvent.eventId);
replaceDraftRequested(Helpers.getEventBodyForEditing(event));
}
enabled: event && !event.redacted && !event.isLocalEcho && getIsEditable(event)
},
Kirigami.Action {
objectName: 'deleteMenuItem'
text: l10n.get('event-delete')
onTriggered: deleteEventRequested(event.isLocalEcho ? event.txnId : event.eventId, event.isLocalEcho)
enabled: event && !event.redacted
},
Kirigami.Action {
objectName: 'reactMenuItem'
text: l10n.get('event-react-action')
onTriggered: reactionPopupComp.createObject(applicationWindow().overlay).open()
enabled: event && !event.redacted
},
Kirigami.Action {
objectName: 'pinUnpinMenuItem'
enabled: !event.isLocalEcho
text: eventPinned ? l10n.get('event-unpin-action') : l10n.get('event-pin-action')
onTriggered: eventPinned ? unpinEventRequested(event.eventId) : pinEventRequested(event.eventId)
},
MenuSeparator {},
Kirigami.Action {
text: l10n.get('event-view-source')
onTriggered: eventSourcePopupComp.createObject(applicationWindow().overlay).open()
},
Kirigami.Action {
objectName: 'viewHistoryMenuItem'
text: l10n.get('event-view-history')
enabled: event && !!event.isEdited
onTriggered: viewEventHistoryRequested(event.eventId)
}
]
contentData: Array.from(staticItems).concat(upper.menuContent)
onClosed: destroy()
}
}
function shouldPopupMenu() {
return !compactMode
}
function maybePopupMenu() {
if (shouldPopupMenu()) {
menuComp.createObject(parent).popup(parent)
}
}
contentItem: RowLayout {
id: bubbleRootLayout
property var replyToOrAnnotatedEventId: currentEvent.replyingToEventId || (currentEvent.relationType === 'm.annotation' ? currentEvent.relatedEventId : '')
ColumnLayout {
Layout.fillWidth: true
MouseArea {
visible: bubbleRootLayout.replyToOrAnnotatedEventId && !compactMode
Layout.fillWidth: true
Layout.margins: 0
Layout.preferredHeight: inReplyToLayout.implicitHeight
Layout.maximumHeight: upper.replyMaxHeight
clip: true
onClicked: eventListView.goToEvent(bubbleRootLayout.replyToOrAnnotatedEventId)
RowLayout {
id: inReplyToLayout
anchors {
top: parent.top
left: parent.left
right: parent.right
}
Rectangle {
Layout.fillHeight: true
Layout.preferredWidth: Kirigami.Units.smallSpacing
color: Kirigami.Theme.activeTextColor
clip: true
}
Loader {
id: inReplyToLoader
clip: true
source: Qt.resolvedUrl('EventViewCompact.qml')
active: bubbleRootLayout.replyToOrAnnotatedEventId && !compactMode
asynchronous: true
Layout.fillWidth: true
property var event: room.messageById(bubbleRootLayout.replyToOrAnnotatedEventId)
property var props: ({
event,
sender: room.member(event.sender || matrixSdk.userId),
stateKeyUser: event.stateKey ? room.member(event.stateKey) : {},
- isGapped: timeline.gaps.includes(event.eventId),
+ isGapped: timeline.gaps.hasOwnProperty(event.eventId),
})
}
}
}
ColumnLayout {
id: children
data: upper.children
}
Kazv.EventReactions {
objectName: 'eventReactions'
visible: !compactMode
Layout.fillWidth: true
reactions: upper.eventReactions
}
}
property var popupAction: Kirigami.Action {
id: popupAction
text: l10n.get('event-popup-action')
icon.name: 'view-more-horizontal-symbolic'
onTriggered: maybePopupMenu()
}
RowLayout {
Layout.alignment: Qt.AlignBottom
Kirigami.Icon {
source: 'emblem-encrypted-locked'
visible: !compactMode && event && event.encrypted
Layout.preferredHeight: inlineBadgeSize
Layout.preferredWidth: inlineBadgeSize
Accessible.role: Accessible.StaticText
Accessible.name: l10n.get('event-encrypted')
ToolTip.text: l10n.get('event-encrypted')
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.timeout: Helpers.toolTipTimeout
ToolTip.visible: hoverHandlerEncIcon.hovered
HoverHandler { id: hoverHandlerEncIcon }
}
ToolButton {
objectName: 'localEchoIndicator'
visible: !!event.isLocalEcho
hoverEnabled: true
enabled: event.isFailed
icon.name: event.isSending ? 'state-sync' : 'state-warning'
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Accessible.role: event.isSending ? Accessible.StaticText : Accessible.Button
Accessible.name: event.isSending ? l10n.get('event-sending') : l10n.get('event-resend')
ToolTip.text: event.isSending ? l10n.get('event-sending') : l10n.get('event-send-failed')
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.timeout: Helpers.toolTipTimeout
ToolTip.visible: hovered
onClicked: {
room.resendMessage(event.txnId);
}
}
Label {
objectName: 'timeIndicator'
visible: shouldDisplayTime
text: event.formattedTime
ToolTip.text: event.formattedDateTime
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.timeout: Helpers.toolTipTimeout
ToolTip.visible: hoverHandlerTime.hovered
HoverHandler { id: hoverHandlerTime }
font: Kirigami.Theme.smallFont
}
Label {
objectName: 'editedIndicator'
visible: !compactMode && event.isEdited
text: l10n.get('event-edited-indicator')
font: Kirigami.Theme.smallFont
}
Kazv.EventReadIndicator {
objectName: 'eventReadIndicator'
shouldShow: !compactMode && !event.isLocalEcho
model: event.readers()
}
ToolButton {
id: moreButton
objectName: 'moreButton'
visible: !compactMode
action: popupAction
display: AbstractButton.IconOnly
}
}
}
}
diff --git a/src/contents/ui/EventViewWrapper.qml b/src/contents/ui/EventViewWrapper.qml
index d423074..63007a7 100644
--- a/src/contents/ui/EventViewWrapper.qml
+++ b/src/contents/ui/EventViewWrapper.qml
@@ -1,19 +1,19 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.2
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.13 as Kirigami
import '.' as Kazv
Kazv.EventView {
sender: room.member(event.sender || matrixSdk.userId)
stateKeyUser: event.stateKey ? room.member(event.stateKey) : {}
- isGapped: timeline?.gaps?.includes(event.eventId)
+ isGapped: timeline?.gaps?.hasOwnProperty(event.eventId)
}
diff --git a/src/contents/ui/RoomTimelineView.qml b/src/contents/ui/RoomTimelineView.qml
index 7805d87..0376ff4 100644
--- a/src/contents/ui/RoomTimelineView.qml
+++ b/src/contents/ui/RoomTimelineView.qml
@@ -1,185 +1,183 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.2
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.13 as Kirigami
import '.' as Kazv
ListView {
id: roomTimelineView
objectName: 'roomTimelineView'
property var timeline
- property var eventIds: timeline.eventIds
-
property string selectedEventId
signal pinEventRequested(string eventId)
signal unpinEventRequested(string eventId)
signal deleteEventRequested(eventIdOrTxnId: string, isLocalEcho: bool)
signal deleteEventRequestedNoConfirm(eventIdOrTxnId: string, isLocalEcho: bool)
property var pinEvent: Kazv.AsyncHandler {
property var eventIds
trigger: () => room.pinEvents(eventIds)
onResolved: (success, data) => {
if (success) {
showPassiveNotification(l10n.get('event-pin-success-prompt'));
} else {
showPassiveNotification(l10n.get('event-pin-failed-prompt', { errorCode: data.errorCode, errorMsg: data.error }));
}
}
}
property var unpinEvent: Kazv.AsyncHandler {
property var eventIds
trigger: () => room.unpinEvents(eventIds)
onResolved: (success, data) => {
if (success) {
showPassiveNotification(l10n.get('event-unpin-success-prompt'));
} else {
showPassiveNotification(l10n.get('event-unpin-failed-prompt', { errorCode: data.errorCode, errorMsg: data.error }));
}
}
}
property var deleteEvent: Kazv.AsyncHandler {
property var eventId
property var reason
trigger: () => room.redactEvent(eventId, reason)
onResolved: (success, data) => {
if (!success) {
showPassiveNotification(l10n.get('event-delete-failed', { errorMsg: data.error, errorCode: data.errorCode }));
}
}
}
property var deleteLocalEcho: Kazv.AsyncHandler {
property var txnId
trigger: () => room.removeLocalEcho(txnId)
onResolved: (success, data) => {
if (!success) {
showPassiveNotification(l10n.get('event-delete-failed', { errorMsg: data.error, errorCode: data.errorCode }));
}
}
}
onPinEventRequested: (eventId) => {
pinEventPopupComp.createObject(Overlay.overlay, { eventId }).open()
}
onUnpinEventRequested: (eventId) => {
unpinEventPopupComp.createObject(Overlay.overlay, { eventId }).open()
}
onDeleteEventRequested: (eventIdOrTxnId, isLocalEcho) => {
confirmDeletionPopupComp.createObject(Overlay.overlay, { eventIdOrTxnId, isLocalEcho }).open()
}
onDeleteEventRequestedNoConfirm: (eventIdOrTxnId, isLocalEcho) => {
doDeleteEvent(eventIdOrTxnId, isLocalEcho);
}
function doDeleteEvent(eventIdOrTxnId, isLocalEcho) {
if (isLocalEcho) {
deleteLocalEcho.txnId = eventIdOrTxnId;
deleteLocalEcho.call();
} else {
deleteEvent.eventId = eventIdOrTxnId;
deleteEvent.call();
}
}
property var pinEventPopupComp: Component {
Kazv.ConfirmationOverlay {
objectName: 'pinEventPopup'
property var eventId
shouldSelfDestroy: true
title: l10n.get('event-pin-confirmation-title')
message: l10n.get('event-pin-confirmation')
confirmActionText: l10n.get('event-pin-confirm-action')
cancelActionText: l10n.get('event-pin-cancel-action')
onAccepted: {
pinEvent.eventIds = [eventId];
pinEvent.call();
}
EventViewWrapper {
Layout.fillWidth: true
event: room.messageById(eventId)
compactMode: true
}
}
}
property var unpinEventPopupComp: Component {
Kazv.ConfirmationOverlay {
objectName: 'unpinEventPopup'
property var eventId
shouldSelfDestroy: true
title: l10n.get('event-unpin-confirmation-title')
message: l10n.get('event-unpin-confirmation')
confirmActionText: l10n.get('event-unpin-confirm-action')
cancelActionText: l10n.get('event-unpin-cancel-action')
onAccepted: {
unpinEvent.eventIds = [eventId];
unpinEvent.call();
}
EventViewWrapper {
Layout.fillWidth: true
event: room.messageById(eventId)
compactMode: true
}
}
}
property var confirmDeletionPopupComp: Component {
Kazv.ConfirmationOverlay {
objectName: 'confirmDeletionPopup'
property string eventIdOrTxnId
property bool isLocalEcho
shouldSelfDestroy: true
title: l10n.get('confirm-deletion-popup-title')
message: l10n.get('confirm-deletion-popup-message')
confirmActionText: l10n.get('confirm-deletion-popup-confirm-action')
cancelActionText: l10n.get('confirm-deletion-popup-cancel-action')
onAccepted: doDeleteEvent(eventIdOrTxnId, isLocalEcho)
EventViewWrapper {
id: eventView
Layout.fillWidth: true
event: isLocalEcho ? room.localEchoById(eventIdOrTxnId) : room.messageById(eventIdOrTxnId)
compactMode: true
}
}
}
spacing: 0
model: timeline
currentIndex: -1
verticalLayoutDirection: ListView.BottomToTop
delegate: EventViewWrapper {
property var eventListView: ListView.view
prevEvent: index < timeline.count - 1 ? timeline.at(index + 1) : undefined
event: timeline.at(index)
width: ListView.view.width
isSelected: roomTimelineView.selectedEventId && event.eventId === roomTimelineView.selectedEventId
}
function goToEvent(eventId) {
selectedEventId = eventId;
const index = timeline.indexOfEvent(eventId);
positionViewAtIndex(index, ListView.Center);
}
}
diff --git a/src/matrix-room-pinned-events-timeline.cpp b/src/matrix-room-pinned-events-timeline.cpp
index b60d57e..1062a03 100644
--- a/src/matrix-room-pinned-events-timeline.cpp
+++ b/src/matrix-room-pinned-events-timeline.cpp
@@ -1,31 +1,32 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include "matrix-room-pinned-events-timeline.hpp"
+#include <QJsonValue>
using namespace Kazv;
MatrixRoomPinnedEventsTimeline::MatrixRoomPinnedEventsTimeline(Kazv::Room room, QObject *parent)
: MatrixRoomTimeline(
room.pinnedEvents(),
room.messagesMap(),
lager::make_constant(immer::flex_vector<LocalEchoDesc>()),
lager::make_constant(immer::map<std::string, std::string>()),
room,
parent
)
, LAGER_QT(eventIds)(room.pinnedEvents().map([](const auto &ids) {
- QSet<QString> res;
+ QJsonObject res;
for (const auto &id : ids) {
- res.insert(QString::fromStdString(id));
+ res.insert(QString::fromStdString(id), QJsonValue(true));
}
return res;
}))
{
}
MatrixRoomPinnedEventsTimeline::~MatrixRoomPinnedEventsTimeline() = default;
diff --git a/src/matrix-room-pinned-events-timeline.hpp b/src/matrix-room-pinned-events-timeline.hpp
index 23cb808..6059d86 100644
--- a/src/matrix-room-pinned-events-timeline.hpp
+++ b/src/matrix-room-pinned-events-timeline.hpp
@@ -1,24 +1,25 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
+#include <QJsonObject>
#include "matrix-room-timeline.hpp"
class MatrixRoomPinnedEventsTimeline : public MatrixRoomTimeline
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
explicit MatrixRoomPinnedEventsTimeline(Kazv::Room room, QObject *parent = 0);
~MatrixRoomPinnedEventsTimeline() override;
- LAGER_QT_READER(QSet<QString>, eventIds);
+ LAGER_QT_READER(QJsonObject, eventIds);
};
diff --git a/src/matrix-room-timeline.cpp b/src/matrix-room-timeline.cpp
index 2437148..e7ff630 100644
--- a/src/matrix-room-timeline.cpp
+++ b/src/matrix-room-timeline.cpp
@@ -1,106 +1,106 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <cmath>
#include <lager/lenses/optional.hpp>
#include <QTimer>
-
+#include <QJsonValue>
#include <cursorutil.hpp>
#include "matrix-room-timeline.hpp"
#include "matrix-event.hpp"
#include "helper.hpp"
#include "kazv-log.hpp"
using namespace Kazv;
MatrixRoomTimeline::MatrixRoomTimeline(lager::reader<immer::flex_vector<std::string>> eventIds, lager::reader<immer::map<std::string, Kazv::Event>> messagesMap, lager::reader<immer::flex_vector<LocalEchoDesc>> localEchoes, lager::reader<immer::map<std::string, std::string>> timelineGaps, Kazv::Room room, QObject *parent)
: KazvAbstractListModel(parent)
, m_room(room)
, m_timelineEventIds(eventIds)
, m_messagesMap(messagesMap)
, m_localEchoes(localEchoes)
, m_timelineGaps(timelineGaps)
, LAGER_QT(gaps)(m_timelineGaps.map([](auto g) {
- QSet<QString> res{};
+ QJsonObject res;
for (const auto &[k, _v] : g) {
- res.insert(QString::fromStdString(k));
+ res.insert(QString::fromStdString(k), QJsonValue(true));
}
return res;
}))
{
initCountCursor(lager::with(
m_timelineEventIds.xform(containerSize),
m_localEchoes.xform(containerSize)
).map([](const auto &tlSize, const auto &localSize) { return tlSize + localSize; }), InitLater);
}
MatrixRoomTimeline::MatrixRoomTimeline(Kazv::Room room, QObject *parent)
: MatrixRoomTimeline(
room.timelineEventIds(),
room.messagesMap(),
room.localEchoes(),
room.timelineGaps(),
room,
parent
)
{
}
MatrixRoomTimeline::~MatrixRoomTimeline() = default;
MatrixEvent *MatrixRoomTimeline::at(int index) const
{
return new MatrixEvent(lager::with(m_timelineEventIds, m_messagesMap, m_localEchoes, LAGER_QT(count))
.map([index](const auto &tlEventIds, const auto &messagesMap, const auto &localEchoes, const auto &count) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> {
auto rIndex = count - index - 1;
if (static_cast<unsigned int>(rIndex) < tlEventIds.size()) {
return messagesMap[tlEventIds[rIndex]];
} else {
auto remainingIndex = rIndex - tlEventIds.size();
if (remainingIndex < localEchoes.size()) {
return localEchoes[remainingIndex];
} else {
return Event();
}
}
}), m_room);
}
int MatrixRoomTimeline::indexOfEvent(const QString &eventId) const
{
auto eventMap = m_messagesMap.get();
auto eventIdStd = eventId.toStdString();
if (eventMap.count(eventIdStd) == 0) {
return -1;
}
auto eventIds = m_timelineEventIds.get();
auto it = std::lower_bound(eventIds.begin(), eventIds.end(), eventIdStd,
[eventMap](const auto &idA, const auto &idB) {
auto a = std::make_pair(eventMap[idA].originServerTs(), idA);
auto b = std::make_pair(eventMap[idB].originServerTs(), idB);
return a < b;
}
);
if (it == eventIds.end()) {
return -1;
} else {
// suppose there are A events and B local echoes
// forward index:
// [0, 1, ..., A-1] [A, A+1, ..., A+B-1]
// reverse index:
// [A+B-1, A+B-2, ..., B] [B-1, B-2 , 0]
// forward = it.index()
// forward + reverse = A+B-1 = count() - 1
return count() - it.index() - 1;
}
}
diff --git a/src/matrix-room-timeline.hpp b/src/matrix-room-timeline.hpp
index b828a48..56f902b 100644
--- a/src/matrix-room-timeline.hpp
+++ b/src/matrix-room-timeline.hpp
@@ -1,43 +1,43 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
-
+#include <QJsonObject>
#include <lager/extra/qt.hpp>
#include <client/room/room.hpp>
#include "kazv-abstract-list-model.hpp"
class MatrixEvent;
class MatrixRoomTimeline : public KazvAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Kazv::Room m_room;
lager::reader<immer::flex_vector<std::string>> m_timelineEventIds;
lager::reader<immer::map<std::string, Kazv::Event>> m_messagesMap;
lager::reader<immer::flex_vector<Kazv::LocalEchoDesc>> m_localEchoes;
lager::reader<immer::map<std::string, std::string>> m_timelineGaps;
public:
explicit MatrixRoomTimeline(Kazv::Room room, QObject *parent = 0);
explicit MatrixRoomTimeline(lager::reader<immer::flex_vector<std::string>> eventIds, lager::reader<immer::map<std::string, Kazv::Event>> messagesMap, lager::reader<immer::flex_vector<Kazv::LocalEchoDesc>> localEchoes, lager::reader<immer::map<std::string, std::string>> timelineGaps, Kazv::Room room, QObject *parent = 0);
~MatrixRoomTimeline() override;
- LAGER_QT_READER(QSet<QString>, gaps);
+ LAGER_QT_READER(QJsonObject, gaps);
Q_INVOKABLE MatrixEvent *at(int index) const;
Q_INVOKABLE int indexOfEvent(const QString &eventId) const;
};
diff --git a/src/tests/quick-tests/tst_EventView.qml b/src/tests/quick-tests/tst_EventView.qml
index e3a8f4c..7902ebc 100644
--- a/src/tests/quick-tests/tst_EventView.qml
+++ b/src/tests/quick-tests/tst_EventView.qml
@@ -1,700 +1,702 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.3
import QtQuick.Layouts 1.15
import QtTest 1.0
import moe.kazv.mxc.kazv 0.0 as MK
import org.kde.kirigami 2.13 as Kirigami
import '../../contents/ui' as Kazv
import 'test-helpers.js' as Helpers
import 'test-helpers' as QmlHelpers
QmlHelpers.TestItem {
id: item
property var activateUserPage: mockHelper.noop()
property var paginateBackRequested: mockHelper.noop()
property var viewEventHistoryRequested: mockHelper.noop()
property var pinEventRequested: mockHelper.noop()
property var unpinEventRequested: mockHelper.noop()
property var deleteEventRequested: mockHelper.noop()
property var timeline: ({
- gaps: [],
+ gaps: {},
})
property var pinnedEvents: ({
count: 2,
- eventIds: ['$123', '$456'],
+ eventIds: {
+ '$123': true,
+ '$456': true,
+ },
})
property var room: ({
resendMessage: mockHelper.promise(),
removeLocalEcho: mockHelper.promise(),
ensureStateEvent: mockHelper.promise(),
messageById: (_id) => item.textEvent,
member: (_id) => ({}),
})
property var localEcho: ({
eventId: '',
sender: '',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: 'some body',
},
encrypted: false,
isState: false,
unsignedData: {},
isLocalEcho: true,
isSending: true,
isFailed: false,
txnId: 'some-txn-id',
})
property var failedLocalEcho: Object.assign({}, item.localEcho, { isSending: false, isFailed: true, })
property var textEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var htmlEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: '**some body**',
format: 'org.matrix.custom.html',
formatted_body: '<strong>some body</strong>',
},
formattedTime: '4:06 P.M.',
})
property var replyHtmlEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
replyingToEventId: '$xxx',
content: {
msgtype: 'm.text',
body: '> some quote\n**some body**',
format: 'org.matrix.custom.html',
formatted_body: '<mx-reply><blockquote>some quote</blockquote></mx-reply><strong>some body</strong>',
},
formattedTime: '4:06 P.M.',
})
property var replyNoQuoteHtmlEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
replyingToEventId: '$xxx',
content: {
msgtype: 'm.text',
body: '**some body**',
format: 'org.matrix.custom.html',
formatted_body: '<strong>some body</strong>',
},
formattedTime: '4:06 P.M.',
})
property var redactedEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {},
redacted: true,
formattedTime: '4:06 P.M.',
})
property var imageEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
info: {
h: Kirigami.Units.gridUnit * 10,
w: Kirigami.Units.gridUnit * 10,
}
},
formattedTime: '4:06 P.M.',
})
property var stickerEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.sticker',
stateKey: '',
content: {
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var eventBySomeoneElse: ({
eventId: '',
sender: '@bar:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var eventTextBySomeoneElse: ({
eventId: '',
sender: '@bar:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var ignoredEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
relationType: 'm.replace',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var unknownTypeEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'moe.kazv.mxc.unknown.event',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
},
})
property var unknownMsgtypeEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'moe.kazv.mxc.unknown.msgtype',
body: 'some body',
},
})
property var cannotDecryptEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'moe.kazv.mxc.cannot.decrypt',
body: 'some body',
},
})
property var editedEvent: ({
eventId: '!some-event-id',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: 'some body',
},
formattedTime: '4:06 P.M.',
isEdited: true,
})
property var pinnedEvent: ({
eventId: '$123',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
content: {
msgtype: 'm.text',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var notPinnedEvent: ({
eventId: '$789',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
content: {
msgtype: 'm.text',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var sender: ({
membership: 'join',
userId: '@foo:tusooa.xyz',
name: 'foo',
avatarMxcUri: '',
})
property var senderOther: ({
membership: 'join',
userId: '@bar:tusooa.xyz',
name: 'bar',
avatarMxcUri: '',
})
property var eventViewComp: Component {
Kazv.EventView {
Layout.fillWidth: true
event: QtObject {
property var eventId: '$some-event-id'
function readers() { return 0; }
}
sender: item.sender
isGapped: true
}
}
property var eventComp: Component {
QtObject {
property var eventId: '$some-event-id'
function readers() { return 0; }
}
}
ColumnLayout {
anchors.fill: parent
Kazv.EventView {
Layout.fillWidth: true
id: eventView
event: item.localEcho
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewFailed
event: item.failedLocalEcho
sender: item.sender
- property int index: 0
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewText
event: item.textEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewHtml
event: item.htmlEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewHtmlReply
event: item.replyHtmlEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewHtmlReplyNoQuote
event: item.replyNoQuoteHtmlEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewRedacted
event: item.redactedEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewUnknownType
event: item.unknownTypeEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewUnknownMsgtype
event: item.unknownMsgtypeEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewCannotDecrypt
event: item.cannotDecryptEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewCollapseSameSender
event: item.imageEvent
sender: item.sender
prevEvent: item.redactedEvent
}
Kazv.EventView {
id: eventViewCollapseLocalEcho
event: item.localEcho
sender: item.sender
prevEvent: item.redactedEvent
}
Kazv.EventView {
id: eventViewDontCollapseDifferentSender
event: item.imageEvent
sender: item.sender
prevEvent: item.eventBySomeoneElse
}
Kazv.EventView {
id: eventViewDontCollapseLocalEchoDifferentSender
event: item.localEcho
sender: item.sender
prevEvent: item.eventBySomeoneElse
}
Kazv.EventView {
id: eventViewDontCollapseIgnoredEvent
event: item.imageEvent
sender: item.sender
prevEvent: item.ignoredEvent
}
Kazv.EventView {
id: eventViewTextBySomeoneElse
event: item.eventTextBySomeoneElse
sender: item.senderOther
}
Kazv.EventView {
id: eventViewImage
event: item.imageEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewImageCompact
compactMode: true
event: item.imageEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewSticker
event: item.stickerEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewStickerCompact
compactMode: true
event: item.stickerEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewEdited
event: item.editedEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewPinned
event: item.pinnedEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewNotPinned
event: item.notPinnedEvent
sender: item.sender
}
}
TestCase {
id: eventViewTest
name: 'EventViewTest'
when: windowShown
function init() {
item.matrixSdk.userId = '@foo:tusooa.xyz';
eventView.event = item.localEcho;
mockHelper.clearAll();
}
function test_localEcho() {
const indicator = findChild(eventView, 'localEchoIndicator');
verify(indicator);
verify(indicator.icon.name === 'state-sync');
verify(indicator.Accessible.name === l10n.get('event-sending'));
const resendButton = findChild(eventView, 'localEchoIndicator');
verify(resendButton);
verify(!resendButton.enabled);
verify(!findChild(eventView, 'timeIndicator').visible);
}
function test_localEchoFailedRemove() {
const indicator = findChild(eventViewFailed, 'localEchoIndicator');
verify(indicator);
verify(indicator.visible);
verify(indicator.icon.name === 'state-warning');
verify(indicator.Accessible.name === l10n.get('event-resend'));
const menuComp = findChild(eventViewFailed, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
const deleteAction = findChild(menu, 'deleteMenuItem');
verify(deleteAction.enabled);
deleteAction.trigger();
compare(deleteEventRequested.calledTimes(), 1);
}
function test_sentMessage() {
const indicator = findChild(eventViewText, 'localEchoIndicator');
verify(indicator);
verify(!indicator.visible);
verify(findChild(eventViewText, 'timeIndicator').visible);
}
function test_htmlMessage() {
const text = findChild(eventViewHtml, 'eventViewMainItem');
verify(text.textFormat === TextEdit.RichText);
verify(text.text.includes('some body'));
}
function test_htmlMessageReply() {
const text = findChild(eventViewHtmlReply, 'eventViewMainItem');
verify(text.textFormat === TextEdit.RichText);
verify(!text.text.includes('<blockquote>'));
}
function test_htmlMessageReplyNoQuote() {
const text = findChild(eventViewHtmlReplyNoQuote, 'eventViewMainItem');
verify(text.textFormat === TextEdit.RichText);
verify(!text.text.includes('<blockquote>'));
verify(text.text.includes('some body'));
}
function test_menuText() {
const menuComp = findChild(eventViewText, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
verify(menu);
tryVerify(() => findChild(menu, 'replyToMenuItem').enabled);
tryVerify(() => findChild(menu, 'editMenuItem').enabled);
tryVerify(() => findChild(menu, 'deleteMenuItem').enabled);
}
function test_menuRedacted() {
const menuComp = findChild(eventViewRedacted, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
verify(menu);
tryVerify(() => !findChild(menu, 'replyToMenuItem').enabled);
tryVerify(() => !findChild(menu, 'deleteMenuItem').enabled);
}
function test_fallback() {
verify(findChild(eventViewUnknownType, 'fallbackIcon'));
verify(findChild(eventViewUnknownType, 'textEventContent').text ===
l10n.get('event-fallback-text', { type: unknownTypeEvent.type }));
verify(findChild(eventViewUnknownMsgtype, 'fallbackIcon'));
verify(findChild(eventViewUnknownMsgtype, 'textEventContent').text ===
l10n.get('event-msgtype-fallback-text', { msgtype: unknownMsgtypeEvent.content.msgtype }));
verify(findChild(eventViewCannotDecrypt, 'fallbackIcon'));
verify(findChild(eventViewCannotDecrypt, 'textEventContent').text ===
l10n.get('event-cannot-decrypt-text'));
}
function test_collapseSender() {
verify(!findChild(eventViewCollapseSameSender, 'senderAvatar').visible);
verify(findChild(eventViewCollapseSameSender, 'senderCollapsedPlaceholder').visible);
verify(!findChild(eventViewCollapseLocalEcho, 'senderAvatar').visible);
verify(findChild(eventViewCollapseLocalEcho, 'senderCollapsedPlaceholder').visible);
verify(findChild(eventViewDontCollapseDifferentSender, 'senderAvatar').visible);
verify(!findChild(eventViewDontCollapseDifferentSender, 'senderCollapsedPlaceholder').visible);
verify(findChild(eventViewDontCollapseLocalEchoDifferentSender, 'senderAvatar').visible);
verify(!findChild(eventViewDontCollapseLocalEchoDifferentSender, 'senderCollapsedPlaceholder').visible);
verify(findChild(eventViewDontCollapseIgnoredEvent, 'senderAvatar').visible);
verify(!findChild(eventViewDontCollapseIgnoredEvent, 'senderCollapsedPlaceholder').visible);
}
function test_eventByOther() {
const menuComp = findChild(eventViewTextBySomeoneElse, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
verify(menu);
tryVerify(() => !findChild(menu, 'editMenuItem').enabled);
}
function test_menuSelfDestruct() {
const menuComp = findChild(eventViewTextBySomeoneElse, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
verify(menu);
menu.popup(item);
menu.dismiss();
const menu2 = menuComp.createObject(menuComp.parent);
menu2.popup(item);
menu2.dismiss();
}
function test_userLink() {
const text = findChild(eventViewText, 'textEventContent');
text.linkActivated('https://matrix.to/#/@mew:example.com');
tryVerify(() => item.room.ensureStateEvent.calledTimes());
verify(Helpers.deepEqual(
item.room.ensureStateEvent.lastArgs(),
['m.room.member', '@mew:example.com']));
item.room.ensureStateEvent.lastRetVal().resolve(true, {});
tryVerify(() => item.activateUserPage.calledTimes());
}
function test_senderNameOverrided() {
const name = findChild(eventViewText, 'userNicknameText');
compare(name.text, l10n.get(
'user-name-overrided',
{ overridedName: 'something', globalName: 'foo' }));
}
function test_senderNameNotOverrided() {
const name = findChild(eventViewTextBySomeoneElse, 'userNicknameText');
compare(name.text, 'bar');
}
function test_paginateBack() {
const obj = item.eventViewComp.createObject(item);
tryVerify(() => item.paginateBackRequested.calledTimes());
verify(item.paginateBackRequested.lastArgs()[0] === '$some-event-id');
item.paginateBackRequested.clear();
obj.event.eventId = '$some-other-event-id';
tryVerify(() => item.paginateBackRequested.calledTimes() === 1);
verify(item.paginateBackRequested.lastArgs()[0] === '$some-other-event-id');
item.paginateBackRequested.clear();
const newEvent = eventComp.createObject(item);
obj.event = newEvent;
tryVerify(() => item.paginateBackRequested.calledTimes() === 1);
verify(item.paginateBackRequested.lastArgs()[0] === '$some-event-id');
item.paginateBackRequested.clear();
obj.isGapped = false;
verify(item.paginateBackRequested.calledTimes() === 0);
}
function test_avatarClick() {
const avatar = findChild(eventViewText, 'senderAvatar');
mouseClick(avatar);
tryVerify(() => item.activateUserPage.calledTimes() === 1);
// verify that the user id is passed
verify(item.activateUserPage.lastArgs()[2] === '@foo:tusooa.xyz');
}
function test_imageEvent() {
verify(!findChild(eventViewImage, 'summaryLabel').visible);
verify(findChild(eventViewImage, 'bodyLabel').visible);
verify(findChild(eventViewImageCompact, 'summaryLabel').visible);
verify(!findChild(eventViewImageCompact, 'bodyLabel').visible);
verify(!findChild(eventViewSticker, 'summaryLabel').visible);
verify(findChild(eventViewSticker, 'bodyLabel').visible);
verify(findChild(eventViewStickerCompact, 'summaryLabel').visible);
verify(!findChild(eventViewStickerCompact, 'bodyLabel').visible);
const image = findChild(eventViewImageCompact, 'mainImage');
verify(image.height <= Kirigami.Units.gridUnit * 5);
}
function test_history() {
{
const menuComp = findChild(eventViewText, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
const action = findChild(menu, 'viewHistoryMenuItem');
verify(!action.enabled);
}
{
const menuComp = findChild(eventViewEdited, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
const action = findChild(menu, 'viewHistoryMenuItem');
verify(action.enabled);
action.trigger();
compare(viewEventHistoryRequested.calledTimes(), 1);
compare(viewEventHistoryRequested.lastArgs()[0], '!some-event-id');
}
}
function test_pinned() {
{
const menuComp = findChild(eventViewPinned, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
const action = findChild(menu, 'pinUnpinMenuItem');
verify(action.enabled);
action.trigger();
compare(unpinEventRequested.calledTimes(), 1);
compare(unpinEventRequested.lastArgs()[0], '$123');
compare(pinEventRequested.calledTimes(), 0);
}
{
const menuComp = findChild(eventViewNotPinned, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
const action = findChild(menu, 'pinUnpinMenuItem');
verify(action.enabled);
action.trigger();
compare(pinEventRequested.calledTimes(), 1);
compare(pinEventRequested.lastArgs()[0], '$789');
compare(pinEventRequested.calledTimes(), 1);
}
{
const menuComp = findChild(eventView, 'bubble').menuComp;
const menu = menuComp.createObject(menuComp.parent);
const action = findChild(menu, 'pinUnpinMenuItem');
verify(!action.enabled);
}
}
}
}
diff --git a/src/tests/quick-tests/tst_SendMessageBox.qml b/src/tests/quick-tests/tst_SendMessageBox.qml
index f71c02d..f7b5c5a 100644
--- a/src/tests/quick-tests/tst_SendMessageBox.qml
+++ b/src/tests/quick-tests/tst_SendMessageBox.qml
@@ -1,246 +1,245 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtTest 1.0
import org.kde.kirigami 2.13 as Kirigami
import '../../contents/ui' as Kazv
import 'test-helpers.js' as JsHelpers
import 'test-helpers' as QmlHelpers
QmlHelpers.TestItem {
id: item
function makeRoom () {
return {
timeline () {
return {
count: 0,
- eventIds: [],
- gaps: [],
+ gaps: {},
at () { return {}; },
};
},
messageById () { return {}; },
member () { return {}; },
localDraft: '',
setLocalDraft (localDraft) {
this.localDraft = localDraft;
},
updateLocalDraftNow () {
},
members () {
return membersModel;
},
setTyping: mockHelper.noop(),
sendTextMessage: mockHelper.noop(),
roomId: '!some-event-id',
encrypted: true,
};
}
property var room: makeRoom()
property var membersModel: ListModel {
ListElement { userId: '@foo:example.com' }
ListElement { userId: '@bar:example.com' }
property string filter
property var count: 2
function at(index) {
return get(index);
}
}
Kazv.SendMessageBox {
id: sendMessageBox
y: 400
room: item.room
}
TestCase {
id: sendMessageBoxTest
name: 'SendMessageBoxTest'
when: windowShown
function init() {
membersModel.count = 2;
findChild(sendMessageBox, 'draftMessage').text = '';
mockHelper.clearAll();
}
function test_empty() {
const textArea = findChild(sendMessageBox, 'draftMessage');
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === '@foo:example.com ');
verify(textArea.cursorPosition === textArea.text.length);
}
function test_cursorAtEnd() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = 'some test';
textArea.cursorPosition = textArea.text.length;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === 'some test @foo:example.com ');
verify(textArea.cursorPosition === textArea.text.length);
}
function test_cursorAtEndSpacingBefore() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = 'some test\n';
textArea.cursorPosition = textArea.text.length;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === 'some test\n@foo:example.com ');
verify(textArea.cursorPosition === textArea.text.length);
}
function test_cursorAtBeginning() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = 'some test';
textArea.cursorPosition = 0;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === '@foo:example.com some test');
verify(textArea.cursorPosition === '@foo:example.com '.length);
}
function test_cursorAtBeginningSpacingAfter() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = '\nsome test';
textArea.cursorPosition = 0;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === '@foo:example.com\nsome test');
verify(textArea.cursorPosition === '@foo:example.com\n'.length);
}
function test_cursorInMiddleNoSpacing() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = 'some\ntest';
textArea.cursorPosition = 1;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === 's @foo:example.com ome\ntest');
verify(textArea.cursorPosition === 's @foo:example.com '.length);
}
function test_cursorInMiddleSpacingBefore() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = 'some\ntest';
textArea.cursorPosition = 5;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === 'some\n@foo:example.com test');
verify(textArea.cursorPosition === 'some\n@foo:example.com '.length);
}
function test_cursorInMiddleSpacingAfter() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = 'some\ntest';
textArea.cursorPosition = 4;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === 'some @foo:example.com\ntest');
verify(textArea.cursorPosition === 'some @foo:example.com\n'.length);
}
function test_cursorInMiddleSpacingAround() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = 'some\n\ntest';
textArea.cursorPosition = 5;
sendMessageBox.mentionUser('@foo:example.com');
tryVerify(() => textArea.text === 'some\n@foo:example.com\ntest');
verify(textArea.cursorPosition === 'some\n@foo:example.com\n'.length);
}
function test_filter() {
const data = [
['some\n\ntest', 5, ''],
['some\n\n@test\n', 11, '@test'],
['some\n\n@test\n', 10, '@tes'],
['some\n\n@test喵 ', 12, '@test喵'],
// separated by a whitespace
['some\n\n@test\n', 12, ''],
['some\n\n@test\t', 12, ''],
['some\n\n@test ', 12, ''],
// multiple @s
['@test@233', 9, '@233'],
];
const textArea = findChild(sendMessageBox, 'draftMessage');
data.forEach(([text, pos, expected]) => {
textArea.text = text;
textArea.cursorPosition = pos;
compare(textArea.filter, expected);
});
}
function test_completion() {
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = '@ex';
textArea.cursorPosition = 3;
const completionPopup = findChild(sendMessageBox, 'completionPopup');
tryVerify(() => completionPopup.opened);
const listView = findChild(completionPopup, 'completionListView');
tryVerify(() => listView);
tryVerify(() => findChild(listView, 'completionItem0'));
tryVerify(() => findChild(listView, 'completionItem1'));
const first = findChild(listView, 'completionItem0');
const second = findChild(listView, 'completionItem1');
verify(findChild(first, 'userIdLabel').text === '@foo:example.com');
verify(findChild(second, 'userIdLabel').text === '@bar:example.com');
mouseClick(second);
tryVerify(() => !completionPopup.opened);
const expected = '@bar:example.com ';
tryCompare(textArea, 'text', expected);
compare(textArea.cursorPosition, expected.length);
}
function test_zeroCompletion() {
membersModel.count = 0;
const textArea = findChild(sendMessageBox, 'draftMessage');
textArea.text = '@ex';
textArea.cursorPosition = 3;
const completionPopup = findChild(sendMessageBox, 'completionPopup');
tryVerify(() => !completionPopup.opened);
}
function test_emptySend() {
mouseClick(findChild(sendMessageBox, 'sendButton'));
verify(room.sendTextMessage.calledTimes() === 0);
const textArea = findChild(sendMessageBox, 'draftMessage');
mouseClick(textArea);
tryVerify(() => textArea.activeFocus);
keySequence('Ctrl+Enter');
verify(room.sendTextMessage.calledTimes() === 0);
keySequence('Ctrl+Return');
verify(room.sendTextMessage.calledTimes() === 0);
}
function test_replyWithFile() {
sendMessageBox.draftRelType = 'm.in_reply_to';
sendMessageBox.draftRelatedTo = '!some-event-id';
const sendFileButton = findChild(sendMessageBox, 'sendMediaFileButton');
const fileDialog = findChild(sendMessageBox, 'fileDialog');
verify(sendFileButton.enabled);
verify(!fileDialog.visible);
mouseClick(sendFileButton);
verify(fileDialog.visible);
fileDialog.accept();
verify(!fileDialog.visible);
compare(kazvIOManager.startNewUploadJob.calledTimes(), 1);
verify(JsHelpers.deepEqual(kazvIOManager.startNewUploadJob.lastArgs(), {
serverUrl: matrixSdk.serverUrl,
fileUrl: Qt.url(''),
token: matrixSdk.token,
roomId: room.roomId,
roomList: sdkVars.roomList,
encrypted: room.encrypted,
draftRelType: 'm.in_reply_to',
draftRelatedTo: '!some-event-id'
}));
}
}
}
diff --git a/src/tests/quick-tests/tst_SendMessageBoxDrafts.qml b/src/tests/quick-tests/tst_SendMessageBoxDrafts.qml
index edefa28..8fb494a 100644
--- a/src/tests/quick-tests/tst_SendMessageBoxDrafts.qml
+++ b/src/tests/quick-tests/tst_SendMessageBoxDrafts.qml
@@ -1,119 +1,118 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtTest 1.0
import org.kde.kirigami 2.13 as Kirigami
import '../../contents/ui' as Kazv
import 'test-helpers.js' as JsHelpers
import 'test-helpers' as QmlHelpers
QmlHelpers.TestItem {
id: item
function makeRoom () {
return {
timeline () {
return {
count: 0,
- eventIds: [],
- gaps: [],
+ gaps: {},
at () { return {}; },
};
},
messageById () { return {}; },
member () { return {}; },
localDraft: '',
setLocalDraft: mockHelper.noop(),
updateLocalDraftNow: mockHelper.func(function () {
this.localDraft = this.setLocalDraft.lastArgs()[0];
}),
};
}
property var room: makeRoom()
property var room2: makeRoom()
property var sendMessageBoxComp: Component {
id: comp
Kirigami.Page {
id: page
Label {
text: 'room 1'
}
footer: Item {
height: childrenRect.height
width: parent.width
Kazv.SendMessageBox {
room: item.room
}
}
}
}
property var sendMessageBoxComp2: Component {
id: comp2
Kirigami.Page {
id: page
Label {
text: 'room 2'
}
footer: Item {
height: childrenRect.height
width: parent.width
Kazv.SendMessageBox {
room: item.room2
}
}
}
}
property var mockMainPageComp: Component {
id: mockMainPageComp
Kirigami.Page {}
}
Kirigami.ApplicationWindow {
id: window
}
TestCase {
id: sendMessageBoxDraftsTest
name: 'SendMessageBoxDraftsTest'
when: windowShown
function test_saveDraftsOnSwitching() {
// simulates the MainPage when starting up kazv
window.pageStack.push(mockMainPageComp);
// a room page is pushed, here we simulate it with only the send
// message box, as other things are unrelevant
window.pageStack.push(comp);
const textArea = findChild(window.pageStack, 'draftMessage');
verify(textArea);
// change text
textArea.text = 'foo';
// verify that the draft is saved internally but not propagated
verify(item.room.localDraft === '');
verify(item.room.setLocalDraft.lastArgs()[0] === 'foo');
// go back to MainPage
window.pageStack.goBack();
verify(window.pageStack.currentIndex === 0);
// push another room page
window.pageStack.push(comp2);
verify(window.pageStack.currentIndex === 1);
// should be the draft for that room, so it is empty
tryVerify(() => findChild(window.pageStack, 'draftMessage').text === '', 1000);
// verify that the draft is propagated
tryVerify(() => item.room.localDraft === 'foo', 1000);
verify(item.room.updateLocalDraftNow.calledTimes() === 1);
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 10, 8:04 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785438
Default Alt Text
(55 KB)

Event Timeline