diff options
Diffstat (limited to 'Swift/Controllers')
-rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 14 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatController.h | 2 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.cpp | 16 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.h | 12 | ||||
-rw-r--r-- | Swift/Controllers/Chat/MUCController.cpp | 14 | ||||
-rw-r--r-- | Swift/Controllers/Chat/MUCController.h | 3 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 145 |
7 files changed, 187 insertions, 19 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 3e58e40..b6ca984 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -513,60 +513,74 @@ void ChatController::handlePresenceChange(std::shared_ptr<Presence> newPresence) } if (!relevantPresence) { return; } if (!newPresence) { newPresence = std::make_shared<Presence>(); newPresence->setType(Presence::Unavailable); } lastShownStatus_ = newPresence->getShow(); chatStateTracker_->handlePresenceChange(newPresence); chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available); std::string newStatusChangeString = getStatusChangeString(newPresence); if (newStatusChangeString != lastStatusChangeString_) { if (lastWasPresence_) { chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp); } else { chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); } lastStatusChangeString_ = newStatusChangeString; lastWasPresence_ = true; } } boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(std::shared_ptr<Message> message) const { return message->getTimestamp(); } +void ChatController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { + lastMessagesIDs_[from.toBare()] = {messageID, addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp)}; +} + +void ChatController::handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { + auto lastMessage = lastMessagesIDs_.find(from.toBare()); + if ((lastMessage != lastMessagesIDs_.end()) && (lastMessage->second.idInStream == idToReplace)) { + replaceMessage(message, lastMessage->second.idInWindow, timeStamp); + } + else { + addMessageHandleIncomingMessage(from, message, messageID, senderIsSelf, label, timeStamp); + } +} + void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) { HistoryMessage::Type type; if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) { type = HistoryMessage::PrivateMessage; } else { type = HistoryMessage::Chat; } if (historyController_) { historyController_->addMessage(message, fromJID, toJID, type, timeStamp); } } bool ChatController::shouldIgnoreMessage(std::shared_ptr<Message> message) { if (!message->getID().empty()) { if (message->getID() == lastHandledMessageID_) { return true; } else { lastHandledMessageID_ = message->getID(); } } return false; } JID ChatController::messageCorrectionJID(const JID& fromJID) { return fromJID.toBare(); } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 1deb0bb..d5011e4 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -29,60 +29,62 @@ namespace Swift { class TimerFactory; class UIEvent; class ChatController : public ChatControllerBase { public: ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); virtual ~ChatController(); virtual void setToJID(const JID& jid) SWIFTEN_OVERRIDE; virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; virtual void setOnline(bool online) SWIFTEN_OVERRIDE; virtual void handleNewFileTransferController(FileTransferController* ftc); virtual void handleWhiteboardSessionRequest(bool senderIsSelf); virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state); virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) SWIFTEN_OVERRIDE; virtual ChatWindow* detachChatWindow() SWIFTEN_OVERRIDE; virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; protected: virtual void cancelReplaces() SWIFTEN_OVERRIDE; virtual JID getBaseJID() SWIFTEN_OVERRIDE; virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; virtual bool shouldIgnoreMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE; private: void handlePresenceChange(std::shared_ptr<Presence> newPresence); std::string getStatusChangeString(std::shared_ptr<Presence> presence); virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual void postSendMessage(const std::string &body, std::shared_ptr<Stanza> sentStanza) SWIFTEN_OVERRIDE; virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) SWIFTEN_OVERRIDE; + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) SWIFTEN_OVERRIDE; + virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) SWIFTEN_OVERRIDE; virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; virtual void preSendMessageRequest(std::shared_ptr<Message>) SWIFTEN_OVERRIDE; virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const SWIFTEN_OVERRIDE; void handleStanzaAcked(std::shared_ptr<Stanza> stanza); virtual void dayTicked() SWIFTEN_OVERRIDE { lastWasPresence_ = false; } void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; void handleFileTransferCancel(std::string /* id */); void handleFileTransferStart(std::string /* id */, std::string /* description */); void handleFileTransferAccept(std::string /* id */, std::string /* filename */); void handleSendFileRequest(std::string filename); void handleWhiteboardSessionAccept(); void handleWhiteboardSessionCancel(); void handleWhiteboardWindowShow(); void handleSettingChanged(const std::string& settingPath); void checkForDisplayingDisplayReceiptsAlert(); void handleBlockingStateChanged(); void handleBlockUserRequest(); void handleUnblockUserRequest(); void handleInviteToChat(const std::vector<JID>& droppedJIDs); void handleWindowClosed(); diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 7ae7dbd..0fc735a 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -249,85 +249,75 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes return; } else { if (!messageEvent->isReadable()) { return; } showChatWindow(); JID from = message->getFrom(); std::vector<std::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>(); for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) { if (!delayPayloads[i]->getFrom()) { continue; } boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); std::ostringstream s; s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << "."; chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); } std::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); // Determine the timestamp boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time(); boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message); if (messageTimeStamp) { timeStamp = *messageTimeStamp; } onActivity(body); std::shared_ptr<Replace> replace = message->getPayload<Replace>(); bool senderIsSelf = isIncomingMessageFromMe(message); + chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); if (replace) { - // Should check if the user has a previous message - std::map<JID, std::string>::iterator lastMessage; - lastMessage = lastMessagesUIID_.find(messageCorrectionJID(from)); - if (lastMessage != lastMessagesUIID_.end()) { - chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); - replaceMessage(chatMessage, lastMessagesUIID_[messageCorrectionJID(from)], timeStamp); - } + handleIncomingReplaceMessage(from, chatMessage, message->getID(), replace->getID(), senderIsSelf, label, timeStamp); } else { - chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); - addMessageHandleIncomingMessage(from, chatMessage, senderIsSelf, label, timeStamp); + addMessageHandleIncomingMessage(from, chatMessage, message->getID(), senderIsSelf, label, timeStamp); } logMessage(body, from, selfJID_, timeStamp, true); } chatWindow_->show(); updateMessageCount(); postHandleIncomingMessage(messageEvent, chatMessage); } -void ChatControllerBase::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { - lastMessagesUIID_[messageCorrectionJID(from)] = addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp); -} - std::string ChatControllerBase::getErrorMessage(std::shared_ptr<ErrorPayload> error) { std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message"); if (!error->getText().empty()) { return error->getText(); } else { switch (error->getCondition()) { case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index d10df8f..88cb95c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -21,126 +21,134 @@ #include <Swiften/Elements/ErrorPayload.h> #include <Swiften/Elements/SecurityLabelsCatalog.h> #include <Swiften/Elements/Stanza.h> #include <Swiften/JID/JID.h> #include <Swiften/MUC/MUCRegistry.h> #include <Swiften/Presence/PresenceOracle.h> #include <Swiften/Queries/IQRouter.h> #include <Swift/Controllers/Highlighting/HighlightManager.h> #include <Swift/Controllers/HistoryController.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> #include <Swift/Controllers/XMPPEvents/MessageEvent.h> namespace Swift { class AutoAcceptMUCInviteDecider; class AvatarManager; class ChatMessageParser; class ChatWindowFactory; class EntityCapsProvider; class EventController; class HighlightManager; class Highlighter; class IQRouter; class NickResolver; class StanzaChannel; class UIEventStream; class ChatControllerBase : public boost::signals2::trackable { public: + class StreamWindowMessageIDPair { + public: + std::string idInStream; + std::string idInWindow; + }; + + public: virtual ~ChatControllerBase(); void showChatWindow(); void activateChatWindow(); bool hasOpenWindow() const; virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info); virtual void handleIncomingOwnMessage(std::shared_ptr<Message> /*message*/) {} void handleIncomingMessage(std::shared_ptr<MessageEvent> message); std::string addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time); void replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time); virtual void setOnline(bool online); void setEnabled(bool enabled); virtual void setToJID(const JID& jid) {toJID_ = jid;} /** Used for determining when something is recent.*/ boost::signals2::signal<void (const std::string& /*activity*/)> onActivity; boost::signals2::signal<void ()> onUnreadCountChanged; boost::signals2::signal<void ()> onWindowClosed; int getUnreadCount(); const JID& getToJID() {return toJID_;} void handleCapsChanged(const JID& jid); void setCanStartImpromptuChats(bool supportsImpromptu); virtual ChatWindow* detachChatWindow(); boost::signals2::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC; protected: ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); /** * Pass the Message appended, and the stanza used to send it. */ virtual void postSendMessage(const std::string&, std::shared_ptr<Stanza>) {} virtual std::string senderDisplayNameFromMessage(const JID& from) = 0; virtual std::string senderHighlightNameFromMessage(const JID& from) = 0; virtual bool isIncomingMessageFromMe(std::shared_ptr<Message>) = 0; virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) {} - virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time); + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) = 0; + virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& chatMessage, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) = 0; virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage&) {} virtual void preSendMessageRequest(std::shared_ptr<Message>) {} virtual bool isFromContact(const JID& from); virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const = 0; virtual void dayTicked() {} virtual void handleBareJIDCapsChanged(const JID& jid) = 0; std::string getErrorMessage(std::shared_ptr<ErrorPayload>); virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {} virtual void cancelReplaces() = 0; /** JID any iq for account should go to - bare except for PMs */ virtual JID getBaseJID(); virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0; ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf); void updateMessageCount(); virtual bool shouldIgnoreMessage(std::shared_ptr<Message> /* message */) { return false; } /** * What JID should be used for last message correction (XEP-0308) tracking. */ virtual JID messageCorrectionJID(const JID& fromJID) = 0; private: IDGenerator idGenerator_; std::string lastSentMessageStanzaID_; void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage); void handleAllMessagesRead(); void handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error); void handleMUCInvitation(Message::ref message); void handleMediatedMUCInvitation(Message::ref message); void handleGeneralMUCInvitation(MUCInviteEvent::ref event); void handleContinuationsBroken(); protected: JID selfJID_; std::vector<std::shared_ptr<StanzaEvent> > unreadMessages_; std::vector<std::shared_ptr<StanzaEvent> > targetedUnreadMessages_; StanzaChannel* stanzaChannel_; IQRouter* iqRouter_; ChatWindowFactory* chatWindowFactory_; ChatWindow* chatWindow_; JID toJID_; bool labelsEnabled_; - std::map<JID, std::string> lastMessagesUIID_; + std::map<JID, StreamWindowMessageIDPair> lastMessagesIDs_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; bool useDelayForLatency_; EventController* eventController_; EntityCapsProvider* entityCapsProvider_; SecurityLabelsCatalog::Item lastLabel_; HistoryController* historyController_; MUCRegistry* mucRegistry_; Highlighter* highlighter_; std::shared_ptr<ChatMessageParser> chatMessageParser_; AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; UIEventStream* eventStream_; bool lastWasPresence_ = false; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index b685e04..fd3dc37 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -560,66 +560,76 @@ void MUCController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messa if (joined_) { std::string nick = message->getFrom().getResource(); if (nick != nick_ && currentOccupants_.find(nick) != currentOccupants_.end()) { completer_->addWord(nick); } } /*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/ receivedActivity(); joined_ = true; if (message->hasSubject() && !message->getPayload<Body>() && !message->getPayload<Thread>()) { if (!isInitialJoin_) { displaySubjectIfChanged(message->getSubject()); } isInitialJoin_ = false; chatWindow_->setSubject(message->getSubject()); doneGettingHistory_ = true; subject_ = message->getSubject(); } if (!doneGettingHistory_ && !message->getPayload<Delay>()) { doneGettingHistory_ = true; } if (!doneGettingHistory_) { checkDuplicates(message); messageEvent->conclude(); } } -void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) { +void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) { if (from.isBare()) { chatWindow_->addSystemMessage(message, ChatWindow::DefaultDirection); } else { - ChatControllerBase::addMessageHandleIncomingMessage(from, message, senderIsSelf, label, time); + lastMessagesIDs_[from] = {messageID, addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), time)}; + } +} + +void MUCController::handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& /*idToReplace*/, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { + auto lastMessage = lastMessagesIDs_.find(from); + if (lastMessage != lastMessagesIDs_.end()) { + replaceMessage(message, lastMessage->second.idInWindow, timeStamp); + } + else { + addMessageHandleIncomingMessage(from, message, messageID, senderIsSelf, label, timeStamp); } } void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) { std::shared_ptr<Message> message = messageEvent->getStanza(); if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { highlighter_->handleSystemNotifications(chatMessage, messageEvent); if (!messageEvent->getNotifications().empty()) { eventController_->handleIncomingEvent(messageEvent); } if (!messageEvent->getConcluded()) { highlighter_->handleSoundNotifications(chatMessage); } } } void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant, const MUCOccupant::Role& oldRole) { clearPresenceQueue(); receivedActivity(); JID jid(nickToJID(nick)); roster_->removeContactFromGroup(jid, roleToGroupName(oldRole)); JID realJID; if (occupant.getRealJID()) { realJID = occupant.getRealJID().get(); } std::string group(roleToGroupName(occupant.getRole())); roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index ba68ec6..b8f2aac 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -54,61 +54,62 @@ namespace Swift { class MUCController : public ChatControllerBase { public: MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* xmppRoster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager); virtual ~MUCController(); boost::signals2::signal<void ()> onUserLeft; boost::signals2::signal<void ()> onUserJoined; boost::signals2::signal<void ()> onImpromptuConfigCompleted; boost::signals2::signal<void (const std::string&, const std::string& )> onUserNicknameChanged; virtual void setOnline(bool online) SWIFTEN_OVERRIDE; virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; void rejoin(); static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent); static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu); static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname); bool isJoined(); const std::string& getNick(); const boost::optional<std::string> getPassword() const; bool isImpromptu() const; std::map<std::string, JID> getParticipantJIDs() const; void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; protected: virtual void preSendMessageRequest(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message> message) const SWIFTEN_OVERRIDE; virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) SWIFTEN_OVERRIDE; - virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE; + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE; + virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) SWIFTEN_OVERRIDE; virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; virtual void cancelReplaces() SWIFTEN_OVERRIDE; virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE; private: void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role); void clearPresenceQueue(); void addPresenceMessage(const std::string& message); void handleWindowOccupantSelectionChanged(ContactRosterItem* item); void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item); void handleWindowClosed(); void handleAvatarChanged(const JID& jid); void handleOccupantJoined(const MUCOccupant& occupant); void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname); void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason); void handleOccupantPresenceChange(std::shared_ptr<Presence> presence); void handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant,const MUCOccupant::Role& oldRole); void handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation,const MUCOccupant::Affiliation& oldAffiliation); void handleJoinComplete(const std::string& nick); void handleJoinFailed(std::shared_ptr<ErrorPayload> error); void handleJoinTimeoutTick(); void handleChangeSubjectRequest(const std::string&); void handleBookmarkRequest(); std::string roleToGroupName(MUCOccupant::Role role); std::string roleToSortName(MUCOccupant::Role role); JID nickToJID(const std::string& nick); std::string roleToFriendlyName(MUCOccupant::Role role); void receivedActivity(); bool messageTargetsMe(std::shared_ptr<Message> message); diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 0356c6a..bf645d0 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -6,69 +6,69 @@ #include <map> #include <set> #include <boost/bind.hpp> #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <hippomocks.h> #include <Swiften/Avatars/AvatarMemoryStorage.h> #include <Swiften/Avatars/NullAvatarManager.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/Client/Client.h> #include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/DummyStanzaChannel.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Crypto/CryptoProvider.h> #include <Swiften/Crypto/PlatformCryptoProvider.h> #include <Swiften/Disco/DummyEntityCapsProvider.h> #include <Swiften/Elements/CarbonsReceived.h> #include <Swiften/Elements/CarbonsSent.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> #include <Swiften/Elements/Forwarded.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> #include <Swiften/Jingle/JingleSessionManager.h> #include <Swiften/MUC/MUCManager.h> +#include <Swiften/Network/DummyTimerFactory.h> #include <Swiften/Presence/DirectedPresenceSender.h> #include <Swiften/Presence/PresenceOracle.h> #include <Swiften/Presence/StanzaChannelPresenceSender.h> #include <Swiften/Queries/DummyIQChannel.h> #include <Swiften/Roster/XMPPRosterImpl.h> #include <Swiften/VCards/VCardManager.h> #include <Swiften/VCards/VCardMemoryStorage.h> #include <Swiften/Whiteboard/WhiteboardSessionManager.h> -#include <Swiften/Network/DummyTimerFactory.h> #include <Swift/Controllers/Chat/ChatController.h> #include <Swift/Controllers/Chat/ChatsManager.h> #include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h> #include <Swift/Controllers/EventNotifier.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Settings/DummySettingsProvider.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> #include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> #include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> #include <Swift/Controllers/UnitTest/MockChatWindow.h> #include <Swift/Controllers/WhiteboardManager.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <SwifTools/Notifier/Notifier.h> using namespace Swift; class DummyNotifier : public Notifier { public: virtual void showMessage( @@ -112,62 +112,65 @@ class ChatsManagerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster); CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster); CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth); CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom); CPPUNIT_TEST(testChatControllerFullJIDBindingOnMessageAndNotReceipt); CPPUNIT_TEST(testChatControllerFullJIDBindingOnTypingAndNotActive); CPPUNIT_TEST(testLocalMUCServiceDiscoveryResetOnDisconnect); CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite); CPPUNIT_TEST(testNotSplittingMUCPresenceJoinLeaveLinesOnChatStateNotifications); // MUC PM Tests CPPUNIT_TEST(testChatControllerPMPresenceHandling); CPPUNIT_TEST(testChatControllerMucPmUnavailableErrorHandling); // Highlighting tests CPPUNIT_TEST(testChatControllerHighlightingNotificationTesting); CPPUNIT_TEST(testChatControllerHighlightingNotificationDeduplicateSounds); CPPUNIT_TEST(testChatControllerHighlightingNotificationKeyword); CPPUNIT_TEST(testChatControllerMeMessageHandling); CPPUNIT_TEST(testRestartingMUCComponentCrash); CPPUNIT_TEST(testChatControllerMeMessageHandlingInMUC); // Carbons tests CPPUNIT_TEST(testCarbonsForwardedIncomingMessageToSecondResource); CPPUNIT_TEST(testCarbonsForwardedOutgoingMessageFromSecondResource); CPPUNIT_TEST(testCarbonsForwardedIncomingDuplicates); // Message correction tests + CPPUNIT_TEST(testChatControllerMessageCorrectionCorrectReplaceID); + CPPUNIT_TEST(testChatControllerMessageCorrectionIncorrectReplaceID); CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceBySameResource); CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceByOtherResource); + CPPUNIT_TEST(testMUCControllerMessageCorrectionNoIDMatchRequired); CPPUNIT_TEST_SUITE_END(); public: void setUp() { mocks_ = new MockRepository(); notifier_ = std::unique_ptr<DummyNotifier>(new DummyNotifier()); jid_ = JID("test@test.com/resource"); stanzaChannel_ = new DummyStanzaChannel(); iqRouter_ = new IQRouter(stanzaChannel_); eventController_ = new EventController(); chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>(); xmppRoster_ = new XMPPRosterImpl(); mucRegistry_ = new MUCRegistry(); nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, nullptr, mucRegistry_); presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); serverDiscoInfo_ = std::make_shared<DiscoInfo>(); presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_); uiEventStream_ = new UIEventStream(); entityCapsProvider_ = new DummyEntityCapsProvider(); chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>(); mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>(); settings_ = new DummySettingsProvider(); profileSettings_ = new ProfileSettingsProvider("a", settings_); chatListWindow_ = new MockChatListWindow(); ftManager_ = new DummyFileTransferManager(); ftOverview_ = new FileTransferOverview(ftManager_); @@ -1282,112 +1285,252 @@ public: std::string body("This is a legible message. >HEH@)oeueu"); message->setBody(body); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); // incoming carbons message from another resource and duplicate of it { auto originalMessage = std::make_shared<Message>(); originalMessage->setFrom(messageJID); originalMessage->setTo(jid2); originalMessage->setID("BDD82F0B-2523-48BF-B8CA-17B23A314BC2"); originalMessage->setType(Message::Chat); std::string forwardedBody = "Some further text."; originalMessage->setBody(forwardedBody); auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); manager_->handleIncomingMessage(messageWrapper); CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_); window->resetLastMessages(); messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); manager_->handleIncomingMessage(messageWrapper); CPPUNIT_ASSERT_EQUAL(std::string(), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_); } } + /** + * This test case ensures correct handling of the ideal case where the replace + * message refers to a message with a known ID. This results in the last + * message being replaced. + */ + void testChatControllerMessageCorrectionCorrectReplaceID() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + auto message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text before edit"); + message->setID("someID"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text after edit"); + message->addPayload(std::make_shared<Replace>("someID")); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); + } + + /** + * This test case ensures correct handling of the case where the replace + * message refers to a message with a unknown ID. The replace message should + * be treated like a non-repalce message in this case, with no replacement + * occuring. + */ + void testChatControllerMessageCorrectionIncorrectReplaceID() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + auto message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text before edit"); + message->setID("someID"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text after failed edit"); + message->addPayload(std::make_shared<Replace>("wrongID")); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text after failed edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); + } + void testChatControllerMessageCorrectionReplaceBySameResource() { JID messageJID("testling@test.com/resource1"); MockChatWindow* window = new MockChatWindow(); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); auto message = std::make_shared<Message>(); message->setFrom(messageJID); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text before edit"); + message->setID("someID"); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); message = std::make_shared<Message>(); message->setFrom(messageJID); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text after edit"); message->addPayload(std::make_shared<Replace>("someID")); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); } void testChatControllerMessageCorrectionReplaceByOtherResource() { JID messageJID("testling@test.com/resource1"); MockChatWindow* window = new MockChatWindow(); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); auto message = std::make_shared<Message>(); message->setFrom(messageJID); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text before edit"); + message->setID("someID"); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); message = std::make_shared<Message>(); message->setFrom(messageJID.toBare().withResource("resource2")); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text after edit"); message->addPayload(std::make_shared<Replace>("someID")); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); } + void testMUCControllerMessageCorrectionNoIDMatchRequired() { + JID mucJID("SomeMUCRoom@test.com"); + manager_->setOnline(true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + + std::vector<JID> jids; + jids.emplace_back("foo@test.com"); + jids.emplace_back("bar@test.com"); + + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + auto nickname = std::string("SomeNickName"); + // Join room + { + auto joinRoomEvent = std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname); + uiEventStream_->send(joinRoomEvent); + } + + auto genRemoteMUCPresence = [=]() { + auto presence = Presence::create(); + presence->setFrom(mucJID.withResource(nickname)); + presence->setTo(jid_); + return presence; + }; + + { + auto presence = genRemoteMUCPresence(); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addStatusCode(110); + userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator)); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + + { + auto presence = genRemoteMUCPresence(); + presence->setFrom(mucJID.withResource("someDifferentNickname")); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addItem(MUCItem(MUCOccupant::Member, JID("foo@bar.com"), MUCOccupant::Moderator)); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource(nickname)); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setID("fooBlaID_1"); + mucMirrored->setBody("Some misssssspelled message."); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("Some misssssspelled message."), window->bodyFromMessage(window->lastAddedMessage_)); + + // Replace message with non-matching ID + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource(nickname)); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setID("fooBlaID_3"); + mucMirrored->setBody("Some correctly spelled message."); + mucMirrored->addPayload(std::make_shared<Replace>("fooBlaID_2")); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("Some correctly spelled message."), window->bodyFromMessage(window->lastReplacedMessage_)); + } + + private: std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) { std::shared_ptr<Message> message = std::make_shared<Message>(); message->setFrom(from); message->setID(id); message->setBody("This will cause the window to open"); message->addPayload(std::make_shared<DeliveryReceiptRequest>()); return message; } size_t st(int i) { return static_cast<size_t>(i); } void handleHighlightAction(const HighlightAction& action) { handledHighlightActions_++; if (action.getSoundFilePath()) { soundsPlayed_.insert(action.getSoundFilePath().get_value_or("")); } } private: JID jid_; std::unique_ptr<DummyNotifier> notifier_; ChatsManager* manager_; DummyStanzaChannel* stanzaChannel_; IQRouter* iqRouter_; EventController* eventController_; ChatWindowFactory* chatWindowFactory_; JoinMUCWindowFactory* joinMUCWindowFactory_; |