diff options
Diffstat (limited to 'Swift/Controllers/Chat')
-rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 9 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.cpp | 42 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.h | 24 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatMessageParser.cpp | 167 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatMessageParser.h | 30 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatsManager.cpp | 8 | ||||
-rw-r--r-- | Swift/Controllers/Chat/MUCController.cpp | 13 | ||||
-rw-r--r-- | Swift/Controllers/Chat/MUCController.h | 4 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp | 487 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 84 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp | 10 |
11 files changed, 471 insertions, 407 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index a498067..d1cd1fe 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -28,7 +28,7 @@ #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> -#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Highlighting/Highlighter.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/StatusUtil.h> @@ -51,7 +51,7 @@ namespace Swift { * The controller does not gain ownership of the stanzaChannel, nor the factory. */ ChatController::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, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, nickResolver, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); @@ -210,9 +210,10 @@ void ChatController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> mess } void ChatController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) { + highlighter_->handleSystemNotifications(chatMessage, messageEvent); eventController_->handleIncomingEvent(messageEvent); if (!messageEvent->getConcluded()) { - handleHighlightActions(chatMessage); + highlighter_->handleSoundNotifications(chatMessage); } } diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index da9064e..5839d6c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -26,8 +26,8 @@ #include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> -#include <Swift/Controllers/HighlightManager.h> -#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Highlighting/HighlightManager.h> +#include <Swift/Controllers/Highlighting/Highlighter.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> @@ -38,13 +38,13 @@ namespace Swift { -ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { +ChatControllerBase::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, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this)); entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); - highlighter_ = highlightManager->createHighlighter(); + highlighter_ = highlightManager->createHighlighter(nickResolver); ChatControllerBase::setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); createDayChangeTimer(); } @@ -204,30 +204,12 @@ bool ChatControllerBase::hasOpenWindow() const { return chatWindow_ && chatWindow_->isVisible(); } -ChatWindow::ChatMessage ChatControllerBase::buildChatWindowChatMessage(const std::string& message, bool senderIsSelf, const HighlightAction& fullMessageHighlightAction) { +ChatWindow::ChatMessage ChatControllerBase::buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf) { ChatWindow::ChatMessage chatMessage; - chatMessage = chatMessageParser_->parseMessageBody(message, highlighter_->getNick(), senderIsSelf); - chatMessage.setFullMessageHighlightAction(fullMessageHighlightAction); + chatMessage = chatMessageParser_->parseMessageBody(message, senderName, senderIsSelf); return chatMessage; } -void ChatControllerBase::handleHighlightActions(const ChatWindow::ChatMessage& chatMessage) { - std::set<std::string> playedSounds; - if (chatMessage.getFullMessageHighlightAction().playSound()) { - highlighter_->handleHighlightAction(chatMessage.getFullMessageHighlightAction()); - playedSounds.insert(chatMessage.getFullMessageHighlightAction().getSoundFile()); - } - for (std::shared_ptr<ChatWindow::ChatMessagePart> part : chatMessage.getParts()) { - std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightMessage = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part); - if (highlightMessage && highlightMessage->action.playSound()) { - if (playedSounds.find(highlightMessage->action.getSoundFile()) == playedSounds.end()) { - highlighter_->handleHighlightAction(highlightMessage->action); - playedSounds.insert(highlightMessage->action.getSoundFile()); - } - } - } -} - void ChatControllerBase::updateMessageCount() { chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); @@ -314,12 +296,6 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes } onActivity(body); - // Highlight - HighlightAction fullMessageHighlight; - if (!isIncomingMessageFromMe(message)) { - fullMessageHighlight = highlighter_->findFirstFullMessageMatchAction(body, senderHighlightNameFromMessage(from)); - } - std::shared_ptr<Replace> replace = message->getPayload<Replace>(); bool senderIsSelf = isIncomingMessageFromMe(message); if (replace) { @@ -327,12 +303,12 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes std::map<JID, std::string>::iterator lastMessage; lastMessage = lastMessagesUIID_.find(from); if (lastMessage != lastMessagesUIID_.end()) { - chatMessage = buildChatWindowChatMessage(body, senderIsSelf, fullMessageHighlight); + chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); replaceMessage(chatMessage, lastMessagesUIID_[from], timeStamp); } } else { - chatMessage = buildChatWindowChatMessage(body, senderIsSelf, fullMessageHighlight); + chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); addMessageHandleIncomingMessage(from, chatMessage, senderIsSelf, label, timeStamp); } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 4255c19..7f118bd 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -28,24 +28,25 @@ #include <Swiften/Presence/PresenceOracle.h> #include <Swiften/Queries/IQRouter.h> -#include <Swift/Controllers/HighlightManager.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 IQRouter; - class StanzaChannel; - class ChatWindowFactory; + class AutoAcceptMUCInviteDecider; class AvatarManager; - class UIEventStream; - class EventController; + class ChatMessageParser; + class ChatWindowFactory; class EntityCapsProvider; + class EventController; class HighlightManager; class Highlighter; - class ChatMessageParser; - class AutoAcceptMUCInviteDecider; + class IQRouter; + class NickResolver; + class StanzaChannel; + class UIEventStream; class ChatControllerBase : public boost::signals2::trackable { public: @@ -73,7 +74,7 @@ namespace Swift { 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, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); + 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, TimerFactory* timerFactory, 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. @@ -96,8 +97,7 @@ namespace Swift { /** 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, bool senderIsSelf, const HighlightAction& fullMessageHighlightAction); - void handleHighlightActions(const ChatWindow::ChatMessage& chatMessage); + ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf); void updateMessageCount(); private: diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp index ec7df6c..1a822a1 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.cpp +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -1,16 +1,18 @@ /* - * Copyright (c) 2013-2016 Isode Limited. + * Copyright (c) 2013-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <algorithm> #include <memory> #include <utility> #include <vector> #include <boost/algorithm/string.hpp> +#include <boost/regex.hpp> #include <Swiften/Base/Regex.h> #include <Swiften/Base/String.h> @@ -19,14 +21,14 @@ namespace Swift { - ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode) - : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { + ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons, std::shared_ptr<HighlightConfiguration> highlightConfiguration, Mode mode) : emoticons_(emoticons), highlightConfiguration_(highlightConfiguration), mode_(mode) { } typedef std::pair<std::string, std::string> StringPair; - ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) { + ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& senderNickname, bool senderIsSelf) { ChatWindow::ChatMessage parsedMessage; + std::string remaining = body; if (boost::starts_with(body, "/me ")) { remaining = String::getSplittedAtFirst(body, ' ').second; @@ -60,8 +62,12 @@ namespace Swift { parsedMessage = emoticonHighlight(parsedMessage); if (!senderIsSelf) { /* do not highlight our own messsages */ - /* do word-based color highlighting */ - parsedMessage = splitHighlight(parsedMessage, nick); + // Highlight keywords and own mentions. + parsedMessage = splitHighlight(parsedMessage); + + // Highlight full message events like, specific sender, general + // incoming group message, or general incoming direct message. + parsedMessage = fullMessageHighlight(parsedMessage, senderNickname); } return parsedMessage; @@ -89,7 +95,7 @@ namespace Swift { boost::regex emoticonRegex(regexString); ChatWindow::ChatMessage newMessage; - for (std::shared_ptr<ChatWindow::ChatMessagePart> part : parsedMessage.getParts()) { + for (const auto& part : parsedMessage.getParts()) { std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { try { @@ -137,61 +143,122 @@ namespace Swift { } parsedMessage.setParts(newMessage.getParts()); } + return parsedMessage; } - ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick) { - ChatWindow::ChatMessage parsedMessage = message; - - for (size_t i = 0; i < highlightRules_->getSize(); ++i) { - const HighlightRule& rule = highlightRules_->getRule(i); - if (rule.getMatchMUC() && !mucMode_) { - continue; /* this rule only applies to MUC's, and this is a CHAT */ - } else if (rule.getMatchChat() && mucMode_) { - continue; /* this rule only applies to CHAT's, and this is a MUC */ - } else if (rule.getAction().getTextBackground().empty() && rule.getAction().getTextColor().empty()) { - continue; /* do not try to highlight text, if no highlight color is specified */ + ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message) { + auto keywordToRegEx = [](const std::string& keyword, bool matchCaseSensitive) { + std::string escaped = Regex::escape(keyword); + boost::regex::flag_type flags = boost::regex::normal; + if (!matchCaseSensitive) { + flags |= boost::regex::icase; } - const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick); - for (const boost::regex& regex : keywordRegex) { - ChatWindow::ChatMessage newMessage; - for (std::shared_ptr<ChatWindow::ChatMessagePart> part : parsedMessage.getParts()) { - std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; - if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { - try { - boost::match_results<std::string::const_iterator> match; - const std::string& text = textPart->text; - std::string::const_iterator start = text.begin(); - while (regex_search(start, text.end(), match, regex)) { - std::string::const_iterator matchStart = match[0].first; - std::string::const_iterator matchEnd = match[0].second; - if (start != matchStart) { - /* If we're skipping over plain text since the previous emoticon, record it as plain text */ - newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); - } - std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = std::make_shared<ChatWindow::ChatHighlightingMessagePart>(); - highlightPart->text = match.str(); - highlightPart->action = rule.getAction(); - newMessage.append(highlightPart); - start = matchEnd; - } - if (start != text.end()) { - /* If there's plain text after the last emoticon, record it */ - newMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); + return boost::regex("\\b" + escaped + "\\b", flags); + }; + + auto highlightKeywordInChatMessage = [&](const ChatWindow::ChatMessage& message, const std::string& keyword, bool matchCaseSensitive, const HighlightAction& action) { + ChatWindow::ChatMessage resultMessage; + + for (const auto& part : message.getParts()) { + std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + try { + boost::match_results<std::string::const_iterator> match; + const std::string& text = textPart->text; + std::string::const_iterator start = text.begin(); + while (regex_search(start, text.end(), match, keywordToRegEx(keyword, matchCaseSensitive))) { + std::string::const_iterator matchStart = match[0].first; + std::string::const_iterator matchEnd = match[0].second; + if (start != matchStart) { + /* If we're skipping over plain text since the previous emoticon, record it as plain text */ + resultMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); } + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart = std::make_shared<ChatWindow::ChatHighlightingMessagePart>(); + highlightPart->text = match.str(); + highlightPart->action = action; + resultMessage.append(highlightPart); + start = matchEnd; } - catch (std::runtime_error) { - /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ - newMessage.append(part); + if (start != text.end()) { + /* If there's plain text after the last emoticon, record it */ + resultMessage.append(std::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); } - } else { - newMessage.append(part); } + catch (std::runtime_error) { + /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ + resultMessage.append(part); + } + } else { + resultMessage.append(part); } - parsedMessage.setParts(newMessage.getParts()); } + return resultMessage; + }; + + ChatWindow::ChatMessage parsedMessage = message; + + // detect mentions of own nickname + HighlightAction ownMentionKeywordAction = highlightConfiguration_->ownMentionAction; + ownMentionKeywordAction.setSoundFilePath(boost::optional<std::string>()); + ownMentionKeywordAction.setSystemNotificationEnabled(false); + if (!getNick().empty() && !highlightConfiguration_->ownMentionAction.isEmpty()) { + auto nicknameHighlightedMessage = highlightKeywordInChatMessage(parsedMessage, nick_, false, ownMentionKeywordAction); + auto highlightedParts = nicknameHighlightedMessage.getParts(); + auto ownNicknamePart = std::find_if(highlightedParts.begin(), highlightedParts.end(), [&](std::shared_ptr<ChatWindow::ChatMessagePart>& part){ + auto highlightPart = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part); + if (highlightPart && highlightPart->text == nick_) { + return true; + } + return false; + }); + if (ownNicknamePart != highlightedParts.end()) { + parsedMessage.setHighlightActionOwnMention(highlightConfiguration_->ownMentionAction); + } + parsedMessage.setParts(nicknameHighlightedMessage.getParts()); + } + + // detect keywords + for (const auto& keywordHighlight : highlightConfiguration_->keywordHighlights) { + if (keywordHighlight.keyword.empty() || keywordHighlight.action.isEmpty()) { + continue; + } + auto newMessage = highlightKeywordInChatMessage(parsedMessage, keywordHighlight.keyword, keywordHighlight.matchCaseSensitive, keywordHighlight.action); + parsedMessage.setParts(newMessage.getParts()); } return parsedMessage; } + + ChatWindow::ChatMessage ChatMessageParser::fullMessageHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& sender) { + auto fullHighlightedMessage = parsedMessage; + + // contact highlighting + for (const auto& contactHighlight : highlightConfiguration_->contactHighlights) { + if (sender == contactHighlight.name) { + fullHighlightedMessage.setHighlightActionSender(contactHighlight.action); + break; + } + } + + // general incoming messages + HighlightAction groupAction; + HighlightAction chatAction; + + switch (mode_) { + case Mode::GroupChat: + groupAction.setSoundFilePath(highlightConfiguration_->playSoundOnIncomingGroupchatMessages ? boost::optional<std::string>("") : boost::optional<std::string>()); + groupAction.setSystemNotificationEnabled(highlightConfiguration_->showNotificationOnIncomingGroupchatMessages); + fullHighlightedMessage.setHighlightActionGroupMessage(groupAction); + break; + + case Mode::Chat: + chatAction.setSoundFilePath(highlightConfiguration_->playSoundOnIncomingDirectMessages ? boost::optional<std::string>("") : boost::optional<std::string>()); + chatAction.setSystemNotificationEnabled(highlightConfiguration_->showNotificationOnIncomingDirectMessages); + fullHighlightedMessage.setHighlightActonDirectMessage(chatAction); + break; + } + + return fullHighlightedMessage; + } } diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h index 4bed669..de5eac9 100644 --- a/Swift/Controllers/Chat/ChatMessageParser.h +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -1,26 +1,44 @@ /* - * Copyright (c) 2013-2014 Isode Limited. + * Copyright (c) 2013-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once +#include <memory> #include <string> +#include <Swift/Controllers/Highlighting/HighlightConfiguration.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { + /** + * @brief The ChatMessageParser class takes an emoticon map, a \ref HighlightConfiguration, and a boolean that indicates if the message context is in a MUC or not. + * The class handles parsing a message string and identifies emoticons, URLs, and various highlights. + */ class ChatMessageParser { public: - ChatMessageParser(const std::map<std::string, std::string>& emoticons, HighlightRulesListPtr highlightRules, bool mucMode = false); - ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false); + enum class Mode { Chat, GroupChat }; + + public: + ChatMessageParser(const std::map<std::string, std::string>& emoticons, std::shared_ptr<HighlightConfiguration> highlightConfiguration, Mode mode = Mode::Chat); + + void setNick(const std::string& nick) { nick_ = nick; } + std::string getNick() const { return nick_; } + + ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& sender = "", bool senderIsSelf = false); + private: ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); - ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick); + ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage); + ChatWindow::ChatMessage fullMessageHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& sender); + + private: std::map<std::string, std::string> emoticons_; - HighlightRulesListPtr highlightRules_; - bool mucMode_; + std::shared_ptr<HighlightConfiguration> highlightConfiguration_; + Mode mode_; + std::string nick_; }; } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index f55df1e..fc96701 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -746,7 +746,7 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) ChatController* ChatsManager::createNewChatController(const JID& contact) { assert(chatControllers_.find(contact) == chatControllers_.end()); - std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */ + std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getConfiguration(), ChatMessageParser::Mode::Chat); /* a message parser that knows this is a chat (not a room/MUC) */ ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, autoAcceptMUCInviteDecider_); chatControllers_[contact] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); @@ -835,8 +835,8 @@ MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::opti if (reuseChatwindow) { chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow); } - std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */ - controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_, mucBookmarkManager_); + std::shared_ptr<ChatMessageParser> chatMessageParser = std::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat); /* a message parser that knows this is a room/MUC (not a chat) */ + controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, nickResolver_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser, isImpromptu, autoAcceptMUCInviteDecider_, vcardManager_, mucBookmarkManager_); if (chatWindowFactoryAdapter) { /* The adapters are only passed to chat windows, which are deleted in their * controllers' dtor, which are deleted in ChatManager's dtor. The adapters diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index df54d73..c476cf3 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -34,7 +34,7 @@ #include <SwifTools/TabComplete.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> -#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Highlighting/Highlighter.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> @@ -80,6 +80,7 @@ MUCController::MUCController ( StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, + NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* uiEventStream, @@ -97,7 +98,7 @@ MUCController::MUCController ( AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager) : - ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) { + ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), nickResolver, presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false), clientBlockListManager_(clientBlockListManager), mucBookmarkManager_(mucBookmarkManager) { parting_ = true; joined_ = false; lastWasPresence_ = false; @@ -134,8 +135,7 @@ MUCController::MUCController ( muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2)); muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); - highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode); - highlighter_->setNick(nick_); + chatMessageParser_->setNick(nick_); if (timerFactory && stanzaChannel_->isAvailable()) { loginCheckTimer_ = std::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); @@ -593,10 +593,11 @@ void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> mess std::shared_ptr<Message> message = messageEvent->getStanza(); if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { if (messageTargetsMe(message) || isImpromptu_) { + highlighter_->handleSystemNotifications(chatMessage, messageEvent); eventController_->handleIncomingEvent(messageEvent); } if (!messageEvent->getConcluded()) { - handleHighlightActions(chatMessage); + highlighter_->handleSoundNotifications(chatMessage); } } } @@ -1111,7 +1112,7 @@ void MUCController::checkDuplicates(std::shared_ptr<Message> newMessage) { void MUCController::setNick(const std::string& nick) { nick_ = nick; - highlighter_->setNick(nick_); + chatMessageParser_->setNick(nick); } Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) { diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index 7ec2eb4..6244f6d 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -54,7 +54,7 @@ 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, 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); + 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; diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp index bc72b33..163a38a 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -1,30 +1,22 @@ /* - * Copyright (c) 2013-2016 Isode Limited. + * Copyright (c) 2013-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ -#include <cppunit/extensions/HelperMacros.h> -#include <cppunit/extensions/TestFactoryRegistry.h> -#include <hippomocks.h> +#include <gtest/gtest.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Highlighting/HighlightConfiguration.h> +#include <Swift/Controllers/Highlighting/HighlightManager.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> using namespace Swift; -class ChatMessageParserTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(ChatMessageParserTest); - CPPUNIT_TEST(testFullBody); - CPPUNIT_TEST(testOneEmoticon); - CPPUNIT_TEST(testBareEmoticon); - CPPUNIT_TEST(testHiddenEmoticon); - CPPUNIT_TEST(testEndlineEmoticon); - CPPUNIT_TEST(testBoundedEmoticons); - CPPUNIT_TEST(testNoColourNoHighlight); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp() { +// Common test state +class ChatMessageParserTest : public ::testing::Test { +protected: + virtual void SetUp() { smile1_ = ":)"; smile1Path_ = "/blah/smile1.png"; smile2_ = ":("; @@ -33,244 +25,54 @@ public: emoticons_[smile2_] = smile2Path_; } - void tearDown() { + virtual void TearDown() { emoticons_.clear(); } - void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + static void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + ASSERT_LT(index, result.getParts().size()); std::shared_ptr<ChatWindow::ChatTextMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->text); + ASSERT_EQ(text, part->text); } - void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { + static void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { + ASSERT_LT(index, result.getParts().size()); std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT(!!part); - CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); - CPPUNIT_ASSERT_EQUAL(path, part->imagePath); + ASSERT_NE(nullptr, part); + ASSERT_EQ(text, part->alternativeText); + ASSERT_EQ(path, part->imagePath); } -#define assertHighlight(RESULT, INDEX, TEXT, EXPECTED_HIGHLIGHT) \ - { \ - std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(RESULT.getParts()[INDEX]); \ - CPPUNIT_ASSERT_EQUAL(std::string(TEXT), part->text); \ - CPPUNIT_ASSERT(EXPECTED_HIGHLIGHT == part->action); \ - } - - void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + static void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + ASSERT_LT(index, result.getParts().size()); std::shared_ptr<ChatWindow::ChatURIMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->target); - } - - static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) - { - HighlightRule rule; - std::vector<std::string> keywords; - keywords.push_back(keyword); - rule.setKeywords(keywords); - rule.setMatchCase(matchCase); - rule.setMatchWholeWords(matchWholeWord); - rule.setMatchChat(true); - rule.getAction().setTextBackground("white"); - return rule; - } - - static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) - { - std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>(); - list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); - return list; - } - - static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) - { - std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>(); - list->addRule(rule1); - list->addRule(rule2); - return list; - } - - static HighlightRulesListPtr ruleListWithNickHighlight(bool withHighlightColour = true) - { - HighlightRule rule; - rule.setMatchChat(true); - rule.setNickIsKeyword(true); - rule.setMatchCase(true); - rule.setMatchWholeWords(true); - if (withHighlightColour) { - rule.getAction().setTextBackground("white"); - } - std::shared_ptr<HighlightManager::HighlightRulesList> list = std::make_shared<HighlightManager::HighlightRulesList>(); - list->addRule(rule); - return list; - } - - void testFullBody() { - const std::string no_special_message = "a message with no special content"; - ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); - assertText(result, 0, no_special_message); - - HighlightRulesListPtr highlightRuleList = ruleListFromKeyword("trigger", false, false); - testling = ChatMessageParser(emoticons_, highlightRuleList); - result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); - assertEmoticon(result, 0, smile1_, smile1Path_); - assertText(result, 1, " shiny "); - assertEmoticon(result, 2, smile2_, smile2Path_); - assertText(result, 3, " "); - assertHighlight(result, 4, "trigger", highlightRuleList->getRule(0).getAction()); - assertText(result, 5, " "); - assertEmoticon(result, 6, smile1_, smile1Path_); - assertText(result, 7, " "); - assertURL(result, 8, "http://wonderland.lit/blah"); - assertText(result, 9, " "); - assertURL(result, 10, "http://denmark.lit"); - assertText(result, 11, " boom boom"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); - result = testling.parseMessageBody("testtriggermessage"); - assertText(result, 0, "test"); - assertHighlight(result, 1, "trigger", highlightRuleList->getRule(0).getAction()); - assertText(result, 2, "message"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true)); - result = testling.parseMessageBody("testtriggermessage"); - assertText(result, 0, "testtriggermessage"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false)); - result = testling.parseMessageBody("TrIgGeR"); - assertText(result, 0, "TrIgGeR"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); - result = testling.parseMessageBody("TrIgGeR"); - assertHighlight(result, 0, "TrIgGeR", highlightRuleList->getRule(0).getAction()); - - testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); - result = testling.parseMessageBody("partialTrIgGeRmatch"); - assertText(result, 0, "partial"); - assertHighlight(result, 1, "TrIgGeR", highlightRuleList->getRule(0).getAction()); - assertText(result, 2, "match"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zero one two three"); - assertText(result, 0, "zero "); - assertHighlight(result, 1, "one", highlightRuleList->getRule(0).getAction()); - assertText(result, 2, " two "); - assertHighlight(result, 3, "three", highlightRuleList->getRule(0).getAction()); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zero oNe two tHrEe"); - assertText(result, 0, "zero "); - assertHighlight(result, 1, "oNe", highlightRuleList->getRule(0).getAction()); - assertText(result, 2, " two "); - assertHighlight(result, 3, "tHrEe", highlightRuleList->getRule(0).getAction()); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false))); - result = testling.parseMessageBody("zero oNe two tHrEe"); - assertText(result, 0, "zero "); - assertHighlight(result, 1, "oNe", highlightRuleList->getRule(0).getAction()); - assertText(result, 2, " two tHrEe"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false))); - result = testling.parseMessageBody("zero oNe two tHrEe"); - assertText(result, 0, "zero oNe two tHrEe"); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zeroonetwothree"); - assertText(result, 0, "zero"); - assertHighlight(result, 1, "one", highlightRuleList->getRule(0).getAction()); - assertText(result, 2, "two"); - assertHighlight(result, 3, "three", highlightRuleList->getRule(0).getAction()); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zeroOnEtwoThReE"); - assertText(result, 0, "zeroOnEtwo"); - assertHighlight(result, 1, "ThReE", highlightRuleList->getRule(0).getAction()); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false))); - result = testling.parseMessageBody("zeroonetwothree"); - assertText(result, 0, "zeroonetwo"); - assertHighlight(result, 1, "three", highlightRuleList->getRule(0).getAction()); - - testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true))); - result = testling.parseMessageBody("zeroonetwothree"); - assertText(result, 0, "zeroonetwothree"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Alice", "Alice"); - assertHighlight(result, 0, "Alice", highlightRuleList->getRule(0).getAction()); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("TextAliceText", "Alice"); - assertText(result, 0, "TextAliceText"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Text Alice Text", "Alice"); - assertText(result, 0, "Text "); - assertHighlight(result, 1, "Alice", highlightRuleList->getRule(0).getAction()); - assertText(result, 2, " Text"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Alice Text", "Alice"); - assertHighlight(result, 0, "Alice", highlightRuleList->getRule(0).getAction()); - assertText(result, 1, " Text"); - - testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); - result = testling.parseMessageBody("Text Alice", "Alice"); - assertText(result, 0, "Text "); - assertHighlight(result, 1, "Alice", highlightRuleList->getRule(0).getAction()); - } - - void testOneEmoticon() { - ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(" :) "); - assertText(result, 0, " "); - assertEmoticon(result, 1, smile1_, smile1Path_); - assertText(result, 2, " "); - } - - - void testBareEmoticon() { - ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); - assertEmoticon(result, 0, smile1_, smile1Path_); - } - - void testHiddenEmoticon() { - ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); - assertText(result, 0, "b:)a"); - } - - void testEndlineEmoticon() { - ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)"); - assertText(result, 0, "Lazy"); - assertEmoticon(result, 1, smile1_, smile1Path_); + ASSERT_EQ(text, part->target); } - void testBoundedEmoticons() { - ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:("); - assertEmoticon(result, 0, smile1_, smile1Path_); - assertText(result, 1, "Lazy"); - assertEmoticon(result, 2, smile2_, smile2Path_); + void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const HighlightAction& action) { + ASSERT_LT(index, result.getParts().size()); + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); + ASSERT_EQ(std::string(text), part->text); + ASSERT_EQ(action, part->action); } - void testEmoticonParenthesis() { - ChatMessageParser testling(emoticons_, std::make_shared<HighlightManager::HighlightRulesList>()); - ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); - assertText(result, 0, "(Like this "); - assertEmoticon(result, 1, smile1_, smile1Path_); - assertText(result, 2, ")"); + static const std::shared_ptr<HighlightConfiguration> highlightConfigFromKeyword(const std::string& keyword, bool matchCase) { + std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>(); + HighlightConfiguration::KeywordHightlight keywordHighlight; + keywordHighlight.keyword = keyword; + keywordHighlight.matchCaseSensitive = matchCase; + keywordHighlight.action.setFrontColor(std::string("#121212")); + config->keywordHighlights.push_back(keywordHighlight); + return config; } - void testNoColourNoHighlight() { - ChatMessageParser testling(emoticons_, ruleListWithNickHighlight(false)); - ChatWindow::ChatMessage result = testling.parseMessageBody("Alice", "Alice"); - assertText(result, 0, "Alice"); + static const std::shared_ptr<HighlightConfiguration> mergeHighlightConfig(const std::shared_ptr<HighlightConfiguration>& configA, const std::shared_ptr<HighlightConfiguration>& configB) { + std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>(); + config->keywordHighlights.insert(config->keywordHighlights.end(), configA->keywordHighlights.begin(), configA->keywordHighlights.end()); + config->keywordHighlights.insert(config->keywordHighlights.end(), configB->keywordHighlights.begin(), configB->keywordHighlights.end()); + return config; } -private: std::map<std::string, std::string> emoticons_; std::string smile1_; std::string smile1Path_; @@ -278,4 +80,213 @@ private: std::string smile2Path_; }; -CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); +TEST_F(ChatMessageParserTest, testNoHighlightingWithEmtpyConfiguration) { + const std::string no_special_message = "a message with no special content"; + ChatMessageParser testling(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(no_special_message); + assertText(result, 0, no_special_message); +} + +TEST_F(ChatMessageParserTest, testSimpleHighlightAndEmojiAndUrlParsing) { + auto highlightConfig = highlightConfigFromKeyword("trigger", false); + auto testling = ChatMessageParser(emoticons_, highlightConfig); + auto result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, " shiny "); + assertEmoticon(result, 2, smile2_, smile2Path_); + assertText(result, 3, " "); + assertHighlight(result, 4, "trigger", highlightConfig->keywordHighlights[0].action); + assertText(result, 5, " "); + assertEmoticon(result, 6, smile1_, smile1Path_); + assertText(result, 7, " "); + assertURL(result, 8, "http://wonderland.lit/blah"); + assertText(result, 9, " "); + assertURL(result, 10, "http://denmark.lit"); + assertText(result, 11, " boom boom"); +} + +TEST_F(ChatMessageParserTest, testNoKeywordHighlightAsPartOfLongerWords) { + auto testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false)); + auto result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "testtriggermessage"); +} + +TEST_F(ChatMessageParserTest, testCaseInsensitiveKeyordHighlight) { + auto config = highlightConfigFromKeyword("trigger", true); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("TrIgGeR"); + assertText(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false)); + result = testling.parseMessageBody("TrIgGeR"); + assertHighlight(result, 0, "TrIgGeR", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testMultipleKeywordHighlights) { + auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false)); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("zero one two three"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "one", config->keywordHighlights[0].action); + assertText(result, 2, " two "); + assertHighlight(result, 3, "three", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testMultipleCaseInsensitiveKeywordHighlights) { + auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false)); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action); + assertText(result, 2, " two "); + assertHighlight(result, 3, "tHrEe", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testMultipleCaseSensitiveKeywordHighlights) { + auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", true)); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action); + assertText(result, 2, " two tHrEe"); + + config = mergeHighlightConfig(highlightConfigFromKeyword("one", true), highlightConfigFromKeyword("three", false)); + testling = ChatMessageParser(emoticons_, config); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero oNe two "); + assertHighlight(result, 1, "tHrEe", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testOneEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(" :) "); + assertText(result, 0, " "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, " "); +} + +TEST_F(ChatMessageParserTest, testBareEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(":)"); + assertEmoticon(result, 0, smile1_, smile1Path_); +} + +TEST_F(ChatMessageParserTest, testHiddenEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody("b:)a"); + assertText(result, 0, "b:)a"); +} + +TEST_F(ChatMessageParserTest, testEndlineEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody("Lazy:)"); + assertText(result, 0, "Lazy"); + assertEmoticon(result, 1, smile1_, smile1Path_); +} + +TEST_F(ChatMessageParserTest, testBoundedEmoticons) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(":)Lazy:("); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, "Lazy"); + assertEmoticon(result, 2, smile2_, smile2Path_); +} + +TEST_F(ChatMessageParserTest, testEmoticonParenthesis) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody("(Like this :))"); + assertText(result, 0, "(Like this "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, ")"); +} + +TEST_F(ChatMessageParserTest, testSenderAndKeywordHighlighting) { + auto config = std::make_shared<HighlightConfiguration>(); + auto contactHighlight = HighlightConfiguration::ContactHighlight(); + contactHighlight.action.setFrontColor(std::string("#f0f0f0")); + contactHighlight.action.setBackColor(std::string("#0f0f0f")); + contactHighlight.name = "Romeo"; + config->contactHighlights.push_back(contactHighlight); + auto keywordHighlight = HighlightConfiguration::KeywordHightlight(); + keywordHighlight.action.setFrontColor(std::string("#abcdef")); + keywordHighlight.action.setBackColor(std::string("#fedcba")); + keywordHighlight.keyword = "XMPP"; + config->keywordHighlights.push_back(keywordHighlight); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo"); + assertText(result, 0, "Heard any news about "); + assertHighlight(result, 1, "xmpp", keywordHighlight.action); + assertText(result, 2, " recently?"); + ASSERT_EQ(contactHighlight.action, result.getHighlightActionSender()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testKeywordWithEmptyActionIsIgnored) { + auto config = std::make_shared<HighlightConfiguration>(); + auto keywordHighlight = HighlightConfiguration::KeywordHightlight(); + keywordHighlight.keyword = "XMPP"; + config->keywordHighlights.push_back(keywordHighlight); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo"); + assertText(result, 0, "Heard any news about xmpp recently?"); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testMeMessageAndOwnMention) { + auto config = std::make_shared<HighlightConfiguration>(); + config->ownMentionAction.setFrontColor(std::string("#f0f0f0")); + config->ownMentionAction.setBackColor(std::string("#0f0f0f")); + config->ownMentionAction.setSoundFilePath(std::string("someSoundFile.wav")); + auto ownMentionActionForPart = config->ownMentionAction; + ownMentionActionForPart.setSoundFilePath(boost::optional<std::string>()); + auto testling = ChatMessageParser(emoticons_, config); + testling.setNick("Juliet"); + auto result = testling.parseMessageBody("/me wonders when Juliet is coming?", "Romeo"); + assertText(result, 0, "wonders when "); + assertHighlight(result, 1, "Juliet", ownMentionActionForPart); + assertText(result, 2, " is coming?"); + ASSERT_EQ(true, result.isMeCommand()); + ASSERT_EQ(config->ownMentionAction, result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testSoundAndNotificationOnDirectMessage) { + auto defaultConfiguration = std::make_shared<HighlightConfiguration>(); + defaultConfiguration->playSoundOnIncomingDirectMessages = true; + defaultConfiguration->showNotificationOnIncomingDirectMessages = true; + defaultConfiguration->ownMentionAction.setFrontColor(std::string("black")); + defaultConfiguration->ownMentionAction.setBackColor(std::string("yellow")); + defaultConfiguration->ownMentionAction.setSoundFilePath(std::string("")); + defaultConfiguration->ownMentionAction.setSystemNotificationEnabled(true); + + auto testling = ChatMessageParser(emoticons_, defaultConfiguration, ChatMessageParser::Mode::Chat); + auto result = testling.parseMessageBody("I wonder when Juliet is coming?", "Romeo"); + + ASSERT_EQ(std::string(""), result.getHighlightActionDirectMessage().getSoundFilePath().get_value_or(std::string("somethingElse"))); + ASSERT_EQ(true, result.getHighlightActionDirectMessage().isSystemNotificationEnabled()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testWithDefaultConfiguration) { + DummySettingsProvider settings; + HighlightManager manager(&settings); + manager.resetToDefaultConfiguration(); + auto testling = ChatMessageParser(emoticons_, manager.getConfiguration(), ChatMessageParser::Mode::GroupChat); + testling.setNick("Juliet"); + auto result = testling.parseMessageBody("Hello, how is it going?", "Romeo"); + assertText(result, 0, "Hello, how is it going?"); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionSender()); + + result = testling.parseMessageBody("Juliet, seen the new interface design?", "Romeo"); + auto mentionKeywordAction = manager.getConfiguration()->ownMentionAction; + mentionKeywordAction.setSoundFilePath(boost::optional<std::string>()); + mentionKeywordAction.setSystemNotificationEnabled(false); + assertHighlight(result, 0, "Juliet", mentionKeywordAction); + assertText(result, 1, ", seen the new interface design?"); + ASSERT_EQ(manager.getConfiguration()->ownMentionAction, result.getHighlightActionOwnMention()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionSender()); +} diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index cff54f8..80f8346 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -131,7 +131,7 @@ public: wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsProvider_); wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); highlightManager_ = new HighlightManager(settings_); - highlightManager_->resetToDefaultRulesList(); + highlightManager_->resetToDefaultConfiguration(); handledHighlightActions_ = 0; soundsPlayed_.clear(); highlightManager_->onHighlight.connect(boost::bind(&ChatsManagerTest::handleHighlightAction, this, _1)); @@ -786,24 +786,17 @@ public: } void testChatControllerHighlightingNotificationTesting() { - HighlightRule keywordRuleA; - keywordRuleA.setMatchChat(true); - std::vector<std::string> keywordsA; - keywordsA.push_back("Romeo"); - keywordRuleA.setKeywords(keywordsA); - keywordRuleA.getAction().setTextColor("yellow"); - keywordRuleA.getAction().setPlaySound(true); - highlightManager_->insertRule(0, keywordRuleA); - - HighlightRule keywordRuleB; - keywordRuleB.setMatchChat(true); - std::vector<std::string> keywordsB; - keywordsB.push_back("Juliet"); - keywordRuleB.setKeywords(keywordsB); - keywordRuleB.getAction().setTextColor("green"); - keywordRuleB.getAction().setPlaySound(true); - keywordRuleB.getAction().setSoundFile("/tmp/someotherfile.wav"); - highlightManager_->insertRule(0, keywordRuleB); + HighlightConfiguration::KeywordHightlight keywordRuleA; + keywordRuleA.keyword = "Romeo"; + keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow")); + keywordRuleA.action.setSoundFilePath(boost::optional<std::string>("")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA); + + HighlightConfiguration::KeywordHightlight keywordRuleB; + keywordRuleB.keyword = "Juliet"; + keywordRuleB.action.setFrontColor(boost::optional<std::string>("green")); + keywordRuleB.action.setSoundFilePath(boost::optional<std::string>("/tmp/someotherfile.wav")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB); JID messageJID = JID("testling@test.com"); @@ -817,28 +810,22 @@ public: manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(2, handledHighlightActions_); - CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.getAction().getSoundFile()) != soundsPlayed_.end()); - CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.getAction().getSoundFile()) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); } void testChatControllerHighlightingNotificationDeduplicateSounds() { - HighlightRule keywordRuleA; - keywordRuleA.setMatchChat(true); - std::vector<std::string> keywordsA; - keywordsA.push_back("Romeo"); - keywordRuleA.setKeywords(keywordsA); - keywordRuleA.getAction().setTextColor("yellow"); - keywordRuleA.getAction().setPlaySound(true); - highlightManager_->insertRule(0, keywordRuleA); - - HighlightRule keywordRuleB; - keywordRuleB.setMatchChat(true); - std::vector<std::string> keywordsB; - keywordsB.push_back("Juliet"); - keywordRuleB.setKeywords(keywordsB); - keywordRuleB.getAction().setTextColor("green"); - keywordRuleB.getAction().setPlaySound(true); - highlightManager_->insertRule(0, keywordRuleB); + auto keywordRuleA = HighlightConfiguration::KeywordHightlight(); + keywordRuleA.keyword = "Romeo"; + keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow")); + keywordRuleA.action.setSoundFilePath(boost::optional<std::string>("")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA); + + auto keywordRuleB = HighlightConfiguration::KeywordHightlight(); + keywordRuleB.keyword = "Juliet"; + keywordRuleB.action.setFrontColor(boost::optional<std::string>("green")); + keywordRuleB.action.setSoundFilePath(boost::optional<std::string>("")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB); JID messageJID = JID("testling@test.com"); @@ -852,8 +839,8 @@ public: manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(1, handledHighlightActions_); - CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.getAction().getSoundFile()) != soundsPlayed_.end()); - CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.getAction().getSoundFile()) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); } void testChatControllerMeMessageHandling() { @@ -922,12 +909,13 @@ public: JID mucJID("mucroom@rooms.test.com"); std::string nickname = "toodles"; + //highlightManager_->resetToDefaultConfiguration(); + // add highlight rule for 'foo' - HighlightRule fooHighlight; - fooHighlight.setKeywords({"foo"}); - fooHighlight.setMatchMUC(true); - fooHighlight.getAction().setTextBackground("green"); - highlightManager_->insertRule(0, fooHighlight); + HighlightConfiguration::KeywordHightlight keywordHighlight; + keywordHighlight.keyword = "foo"; + keywordHighlight.action.setBackColor(boost::optional<std::string>("green")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordHighlight); MockChatWindow* window = new MockChatWindow(); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); @@ -1141,8 +1129,8 @@ private: void handleHighlightAction(const HighlightAction& action) { handledHighlightActions_++; - if (action.playSound()) { - soundsPlayed_.insert(action.getSoundFile()); + if (action.getSoundFilePath()) { + soundsPlayed_.insert(action.getSoundFilePath().get_value_or("")); } } diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index dad021f..eabf4c5 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -94,18 +94,20 @@ public: highlightManager_ = new HighlightManager(settings_); muc_ = std::make_shared<MockMUC>(mucJID_); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true); + chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat); vcardStorage_ = new VCardMemoryStorage(crypto_.get()); vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); + nickResolver_ = new NickResolver(self_, xmppRoster_, vcardManager_, mucRegistry_); clientBlockListManager_ = new ClientBlockListManager(iqRouter_); mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_); - controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_); + controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, nickResolver_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_); } void tearDown() { delete controller_; delete mucBookmarkManager_; delete clientBlockListManager_; + delete nickResolver_; delete vcardManager_; delete vcardStorage_; delete highlightManager_; @@ -592,7 +594,7 @@ private: ChatWindowFactory* chatWindowFactory_; UserSearchWindowFactory* userSearchWindowFactory_; MUCController* controller_; -// NickResolver* nickResolver_; + NickResolver* nickResolver_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; StanzaChannelPresenceSender* presenceSender_; |