diff options
author | Thanos Doukoudakis <thanos.doukoudakis@isode.com> | 2017-03-24 14:15:52 (GMT) |
---|---|---|
committer | Thanos Doukoudakis <thanos.doukoudakis@isode.com> | 2017-04-06 14:27:45 (GMT) |
commit | 7d2e5e200d8449a4492c6fafa4811197e6fbe40b (patch) | |
tree | a4cc9d8d6eb6d98d60dee9e2b928a9e96392ebe9 | |
parent | 20c50e3960ee68d26fc93738f03d27a6833b28bb (diff) | |
download | swift-7d2e5e200d8449a4492c6fafa4811197e6fbe40b.zip swift-7d2e5e200d8449a4492c6fafa4811197e6fbe40b.tar.bz2 |
Reset the chat state to active after a few seconds
Fix for swift-217
When a user sends a composing Chat State Notification a timer will start. If the user doesn't
send or cancel the message before the timer expires, an active CSN will
be sent.
Test Info:
Build on Windows and unit test pass.
Tested the new functionality with Windows and Linux Client.
Added some test cases to cover the scenario that user goes idle while a
CSN composing state has been sent.
Updated ChatStateNotifierTest to use gtest.
Updated ChatsManagerTest to use a valid TimerFactory object instead of
nullptr.
Change-Id: I35201947e4f042805a6d9df1340a0335effcd657
-rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 2 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 8 | ||||
-rw-r--r-- | Swiften/Chat/ChatStateNotifier.cpp | 25 | ||||
-rw-r--r-- | Swiften/Chat/ChatStateNotifier.h | 8 | ||||
-rw-r--r-- | Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp | 279 |
5 files changed, 186 insertions, 136 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 9cef9fc..8cbf059 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -28,61 +28,61 @@ #include <Swiften/FileTransfer/FileTransferManager.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/Highlighting/Highlighter.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/StatusUtil.h> #include <Swift/Controllers/Translator.h> #include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> #include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> #include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h> 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, 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); + chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider, timerFactory, 20000); chatStateTracker_ = new ChatStateTracker(); nickResolver_ = nickResolver; presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1)); chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1)); stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1)); nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); std::string nick = nickResolver_->jidToNick(toJID_); chatWindow_->setName(nick); std::string startMessage; Presence::ref theirPresence; if (isInMUC) { startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString()); theirPresence = presenceOracle->getLastPresence(contact); } else { startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString()); theirPresence = contact.isBare() ? presenceOracle->getAccountPresence(contact) : presenceOracle->getLastPresence(contact); } Idle::ref idle; if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % Swift::Translator::getInstance()->ptimeToHumanReadableString(idle->getSince())); } startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); if (theirPresence && !theirPresence->getStatus().empty()) { startMessage += " (" + theirPresence->getStatus() + ")"; } lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None; chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); startMessage += "."; chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 1958408..2f77ec7 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -14,60 +14,61 @@ #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/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( @@ -152,94 +153,97 @@ public: 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_); avatarManager_ = new NullAvatarManager(); eventNotifier_ = new EventNotifier(eventController_, notifier_.get(), avatarManager_, nickResolver_); wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsProvider_); wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); highlightManager_ = new HighlightManager(settings_); highlightManager_->resetToDefaultConfiguration(); handledHighlightActions_ = 0; soundsPlayed_.clear(); highlightManager_->onHighlight.connect(boost::bind(&ChatsManagerTest::handleHighlightAction, this, _1)); crypto_ = PlatformCryptoProvider::create(); vcardStorage_ = new VCardMemoryStorage(crypto_); vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_); mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); clientBlockListManager_ = new ClientBlockListManager(iqRouter_); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, nullptr, mucRegistry_, entityCapsProvider_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, nullptr, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, vcardManager_); + timerFactory_ = new DummyTimerFactory(); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, timerFactory_, mucRegistry_, entityCapsProvider_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, nullptr, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, vcardManager_); manager_->setAvatarManager(avatarManager_); } void tearDown() { delete highlightManager_; delete profileSettings_; delete eventNotifier_; delete avatarManager_; delete manager_; + delete timerFactory_; delete clientBlockListManager_; delete vcardManager_; delete vcardStorage_; delete crypto_; delete ftOverview_; delete ftManager_; delete wbSessionManager_; delete wbManager_; delete directedPresenceSender_; delete presenceSender_; delete presenceOracle_; delete nickResolver_; delete mucRegistry_; delete iqRouter_; delete stanzaChannel_; delete eventController_; delete uiEventStream_; delete mucManager_; delete xmppRoster_; delete entityCapsProvider_; delete chatListWindow_; delete mocks_; delete settings_; + } void testFirstOpenWindowIncoming() { JID messageJID("testling@test.com/resource1"); MockChatWindow* window = new MockChatWindow(); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); std::shared_ptr<Message> message(new Message()); message->setFrom(messageJID); std::string body("This is a legible message. >HEH@)oeueu"); message->setBody(body); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); } void testSecondOpenWindowIncoming() { JID messageJID1("testling@test.com/resource1"); MockChatWindow* window1 = new MockChatWindow(); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); std::shared_ptr<Message> message1(new Message()); message1->setFrom(messageJID1); std::string body1("This is a legible message. >HEH@)oeueu"); message1->setBody(body1); manager_->handleIncomingMessage(message1); CPPUNIT_ASSERT_EQUAL(body1, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); JID messageJID2("testling@test.com/resource2"); @@ -1295,34 +1299,36 @@ private: PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; EventNotifier* eventNotifier_; std::shared_ptr<DiscoInfo> serverDiscoInfo_; XMPPRosterImpl* xmppRoster_; PresenceSender* presenceSender_; MockRepository* mocks_; UIEventStream* uiEventStream_; ChatListWindowFactory* chatListWindowFactory_; WhiteboardWindowFactory* whiteboardWindowFactory_; MUCSearchWindowFactory* mucSearchWindowFactory_; MUCRegistry* mucRegistry_; DirectedPresenceSender* directedPresenceSender_; DummyEntityCapsProvider* entityCapsProvider_; MUCManager* mucManager_; DummySettingsProvider* settings_; ProfileSettingsProvider* profileSettings_; ChatListWindow* chatListWindow_; FileTransferOverview* ftOverview_; FileTransferManager* ftManager_; WhiteboardSessionManager* wbSessionManager_; WhiteboardManager* wbManager_; HighlightManager* highlightManager_; ClientBlockListManager* clientBlockListManager_; VCardManager* vcardManager_; CryptoProvider* crypto_; VCardStorage* vcardStorage_; std::map<std::string, std::string> emoticons_; int handledHighlightActions_; std::set<std::string> soundsPlayed_; + DummyTimerFactory* timerFactory_; + }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swiften/Chat/ChatStateNotifier.cpp b/Swiften/Chat/ChatStateNotifier.cpp index cbb9b0b..48a65ab 100644 --- a/Swiften/Chat/ChatStateNotifier.cpp +++ b/Swiften/Chat/ChatStateNotifier.cpp @@ -1,89 +1,112 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swiften/Chat/ChatStateNotifier.h> +#include <cassert> #include <memory> #include <boost/bind.hpp> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/Elements/Message.h> +#include <Swiften/Network/Timer.h> +#include <Swiften/Network/TimerFactory.h> + namespace Swift { -ChatStateNotifier::ChatStateNotifier(StanzaChannel* stanzaChannel, const JID& contact, EntityCapsProvider* entityCapsManager) : stanzaChannel_(stanzaChannel), entityCapsManager_(entityCapsManager), contact_(contact) { + +ChatStateNotifier::ChatStateNotifier(StanzaChannel* stanzaChannel, const JID& contact, EntityCapsProvider* entityCapsManager, TimerFactory* timerFactory, int idleTimeInMilliSecs) : stanzaChannel_(stanzaChannel), entityCapsManager_(entityCapsManager), contact_(contact) { setContact(contact); entityCapsManager_->onCapsChanged.connect(boost::bind(&ChatStateNotifier::handleCapsChanged, this, _1)); + assert(timerFactory); + idleTimer_ = timerFactory->createTimer(idleTimeInMilliSecs); + assert(!!idleTimer_); + idleTimer_->onTick.connect(boost::bind(&ChatStateNotifier::userBecameIdleWhileTyping, this)); } ChatStateNotifier::~ChatStateNotifier() { entityCapsManager_->onCapsChanged.disconnect(boost::bind(&ChatStateNotifier::handleCapsChanged, this, _1)); + idleTimer_->stop(); + idleTimer_->onTick.disconnect(boost::bind(&ChatStateNotifier::userBecameIdleWhileTyping, this)); } void ChatStateNotifier::setContact(const JID& contact) { contactHasSentActive_ = false; userIsTyping_ = false; contactIsOnline_ = false; contact_ = contact; handleCapsChanged(contact_); } void ChatStateNotifier::setContactIsOnline(bool online) { contactIsOnline_ = online; } void ChatStateNotifier::setUserIsTyping() { + idleTimer_->stop(); bool should = contactShouldReceiveStates(); if (should && !userIsTyping_) { userIsTyping_ = true; changeState(ChatState::Composing); } + if (should) { + idleTimer_->start(); + } } void ChatStateNotifier::userSentMessage() { + idleTimer_->stop(); userIsTyping_ = false; } void ChatStateNotifier::userCancelledNewMessage() { + idleTimer_->stop(); if (userIsTyping_) { userIsTyping_ = false; changeState(ChatState::Active); } } +void ChatStateNotifier::userBecameIdleWhileTyping() { + // For now we are returning to active state. When support for the Paused, Inactive and Gone states + // is implemeted, this function should Implement the Pause/Inactive functionality. + userCancelledNewMessage(); +} + void ChatStateNotifier::receivedMessageFromContact(bool hasActiveElement) { contactHasSentActive_ = hasActiveElement; } bool ChatStateNotifier::contactShouldReceiveStates() { /* So, yes, the XEP says to look at caps, but it also says that once you've heard from the contact, the active state overrides this. *HOWEVER* it says that the MUST NOT send csn if you haven't received active is OPTIONAL behaviour for if you haven't got caps.*/ return contactIsOnline_ && (contactHasSentActive_ || contactHas85Caps_); } void ChatStateNotifier::changeState(ChatState::ChatStateType state) { std::shared_ptr<Message> message(std::make_shared<Message>()); message->setTo(contact_); message->addPayload(std::make_shared<ChatState>(state)); stanzaChannel_->sendMessage(message); } void ChatStateNotifier::addChatStateRequest(Message::ref message) { if (contactShouldReceiveStates()) { message->addPayload(std::make_shared<ChatState>(ChatState::Active)); } } void ChatStateNotifier::handleCapsChanged(const JID& jid) { if (jid == contact_) { DiscoInfo::ref caps = entityCapsManager_->getCaps(contact_); bool hasCSN = caps && caps->hasFeature(DiscoInfo::ChatStatesFeature); diff --git a/Swiften/Chat/ChatStateNotifier.h b/Swiften/Chat/ChatStateNotifier.h index a7af9e4..2b24c76 100644 --- a/Swiften/Chat/ChatStateNotifier.h +++ b/Swiften/Chat/ChatStateNotifier.h @@ -1,52 +1,58 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once #include <memory> #include <boost/signals2.hpp> #include <Swiften/Base/API.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/Elements/Message.h> #include <Swiften/JID/JID.h> namespace Swift { class StanzaChannel; class EntityCapsProvider; + class TimerFactory; + class Timer; + class SWIFTEN_API ChatStateNotifier { public: - ChatStateNotifier(StanzaChannel* stanzaChannel, const JID& contact, EntityCapsProvider* entityCapsManager); + ChatStateNotifier(StanzaChannel* stanzaChannel, const JID& contact, EntityCapsProvider* entityCapsManager, TimerFactory* timerFactory, int idleTimeInMilliSecs); ~ChatStateNotifier(); void setContact(const JID& contact); void addChatStateRequest(Message::ref message); void setUserIsTyping(); void userSentMessage(); void userCancelledNewMessage(); void receivedMessageFromContact(bool hasActiveElement); void setContactIsOnline(bool online); private: + void userBecameIdleWhileTyping(); bool contactShouldReceiveStates(); void changeState(ChatState::ChatStateType type); void handleCapsChanged(const JID& contact); private: StanzaChannel* stanzaChannel_; EntityCapsProvider* entityCapsManager_; JID contact_; bool contactHas85Caps_; bool contactHasSentActive_; bool userIsTyping_; bool contactIsOnline_; + std::shared_ptr<Timer> idleTimer_; + }; } diff --git a/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp index 7eeb531..efd37d9 100644 --- a/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp +++ b/Swiften/Chat/UnitTest/ChatStateNotifierTest.cpp @@ -1,168 +1,183 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <boost/bind.hpp> -#include <cppunit/extensions/HelperMacros.h> -#include <cppunit/extensions/TestFactoryRegistry.h> +#include <gtest/gtest.h> #include <Swiften/Chat/ChatStateNotifier.h> #include <Swiften/Client/DummyStanzaChannel.h> #include <Swiften/Disco/DummyEntityCapsProvider.h> +#include <Swiften/Network/DummyTimerFactory.h> using namespace Swift; -class ChatStateNotifierTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(ChatStateNotifierTest); - CPPUNIT_TEST(testStartTypingReply_CapsNotIncluded); - CPPUNIT_TEST(testStartTypingReply_CapsIncluded); - CPPUNIT_TEST(testCancelledNewMessage); - CPPUNIT_TEST(testContinueTypingReply_CapsIncluded); - CPPUNIT_TEST(testTypeReplies_WentOffline); - CPPUNIT_TEST(testContactShouldReceiveStates_CapsOnly); - CPPUNIT_TEST(testContactShouldReceiveStates_CapsNorActive); - CPPUNIT_TEST(testContactShouldReceiveStates_ActiveOverrideOn); - CPPUNIT_TEST(testContactShouldReceiveStates_ActiveOverrideOff); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp() { +class ChatStateNotifierTest : public ::testing::Test { + +protected: + virtual void SetUp() { stanzaChannel = new DummyStanzaChannel(); stanzaChannel->setAvailable(true); entityCapsProvider = new DummyEntityCapsProvider(); - notifier_ = new ChatStateNotifier(stanzaChannel, JID("foo@bar.com/baz"), entityCapsProvider); + timerFactory_ = new DummyTimerFactory(); + notifier_ = new ChatStateNotifier(stanzaChannel, JID("foo@bar.com/baz"), entityCapsProvider, timerFactory_, 2); notifier_->setContactIsOnline(true); } - void tearDown() { + virtual void TearDown() { delete notifier_; + delete timerFactory_; delete entityCapsProvider; delete stanzaChannel; } - void testStartTypingReply_CapsNotIncluded() { - notifier_->setUserIsTyping(); - CPPUNIT_ASSERT_EQUAL(0, getComposingCount()); - } - - void testSendTwoMessages() { - setContactHas85Caps(); - notifier_->setUserIsTyping(); - notifier_->userSentMessage(); - notifier_->setUserIsTyping(); - notifier_->userSentMessage(); - CPPUNIT_ASSERT_EQUAL(2, getComposingCount()); - } - - void testCancelledNewMessage() { - setContactHas85Caps(); - notifier_->setUserIsTyping(); - notifier_->userCancelledNewMessage(); - CPPUNIT_ASSERT_EQUAL(1, getComposingCount()); - CPPUNIT_ASSERT_EQUAL(1, getActiveCount()); - CPPUNIT_ASSERT_EQUAL(ChatState::Active, stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size()-1]->getPayload<ChatState>()->getChatState()); - } - - - void testContactShouldReceiveStates_CapsOnly() { - setContactHas85Caps(); - std::shared_ptr<Message> message(new Message()); - notifier_->addChatStateRequest(message); - CPPUNIT_ASSERT(message->getPayload<ChatState>()); - CPPUNIT_ASSERT_EQUAL(ChatState::Active, message->getPayload<ChatState>()->getChatState()); - } - - void testContactShouldReceiveStates_CapsNorActive() { - std::shared_ptr<Message> message(new Message()); - notifier_->addChatStateRequest(message); - CPPUNIT_ASSERT(!message->getPayload<ChatState>()); - } - - void testContactShouldReceiveStates_ActiveOverrideOn() { - notifier_->receivedMessageFromContact(true); - std::shared_ptr<Message> message(new Message()); - notifier_->addChatStateRequest(message); - CPPUNIT_ASSERT(message->getPayload<ChatState>()); - CPPUNIT_ASSERT_EQUAL(ChatState::Active, message->getPayload<ChatState>()->getChatState()); - } - - void testContactShouldReceiveStates_ActiveOverrideOff() { - setContactHas85Caps(); - notifier_->receivedMessageFromContact(false); - /* I originally read the MUST NOT send after receiving without Active and - * thought this should check for false, but I later found it was OPTIONAL - * (MAY) behaviour only for if you didn't receive caps. - */ - std::shared_ptr<Message> message(new Message()); - notifier_->addChatStateRequest(message); - CPPUNIT_ASSERT(message->getPayload<ChatState>()); - CPPUNIT_ASSERT_EQUAL(ChatState::Active, message->getPayload<ChatState>()->getChatState()); - } - - - void testStartTypingReply_CapsIncluded() { - setContactHas85Caps(); - notifier_->setUserIsTyping(); - CPPUNIT_ASSERT_EQUAL(1, getComposingCount()); - } - - void testContinueTypingReply_CapsIncluded() { - setContactHas85Caps(); - notifier_->setUserIsTyping(); - notifier_->setUserIsTyping(); - notifier_->setUserIsTyping(); - CPPUNIT_ASSERT_EQUAL(1, getComposingCount()); - notifier_->userSentMessage(); - notifier_->setUserIsTyping(); - CPPUNIT_ASSERT_EQUAL(2, getComposingCount()); - + void setContactHas85Caps() { + DiscoInfo::ref caps(new DiscoInfo()); + caps->addFeature(DiscoInfo::ChatStatesFeature); + entityCapsProvider->caps[JID("foo@bar.com/baz")] = caps; + entityCapsProvider->onCapsChanged(JID("foo@bar.com/baz")); } - void testTypeReplies_WentOffline() { - setContactHas85Caps(); - notifier_->setUserIsTyping(); - CPPUNIT_ASSERT_EQUAL(1, getComposingCount()); - notifier_->setContactIsOnline(false); - notifier_->userSentMessage(); - notifier_->setUserIsTyping(); - CPPUNIT_ASSERT_EQUAL(1, getComposingCount()); - } - - private: - void setContactHas85Caps() { - DiscoInfo::ref caps(new DiscoInfo()); - caps->addFeature(DiscoInfo::ChatStatesFeature); - entityCapsProvider->caps[JID("foo@bar.com/baz")] = caps; - entityCapsProvider->onCapsChanged(JID("foo@bar.com/baz")); - } - - int getComposingCount() const { - int result = 0; - for (auto&& stanza : stanzaChannel->sentStanzas) { - if (stanza->getPayload<ChatState>() && stanza->getPayload<ChatState>()->getChatState() == ChatState::Composing) { - result++; - } + int getComposingCount() const { + int result = 0; + for (auto&& stanza : stanzaChannel->sentStanzas) { + if (stanza->getPayload<ChatState>() && stanza->getPayload<ChatState>()->getChatState() == ChatState::Composing) { + result++; } - return result; } + return result; + } - int getActiveCount() const { - int result = 0; - for (auto&& stanza : stanzaChannel->sentStanzas) { - if (stanza->getPayload<ChatState>() && stanza->getPayload<ChatState>()->getChatState() == ChatState::Active) { - result++; - } + int getActiveCount() const { + int result = 0; + for (auto&& stanza : stanzaChannel->sentStanzas) { + if (stanza->getPayload<ChatState>() && stanza->getPayload<ChatState>()->getChatState() == ChatState::Active) { + result++; } - return result; } + return result; + } - private: - DummyStanzaChannel* stanzaChannel; - DummyEntityCapsProvider* entityCapsProvider; - ChatStateNotifier* notifier_; + DummyStanzaChannel* stanzaChannel; + DummyEntityCapsProvider* entityCapsProvider; + DummyTimerFactory* timerFactory_; + ChatStateNotifier* notifier_; }; -CPPUNIT_TEST_SUITE_REGISTRATION(ChatStateNotifierTest); +TEST_F(ChatStateNotifierTest, testStartTypingReply_CapsNotIncluded) { + notifier_->setUserIsTyping(); + ASSERT_EQ(0, getComposingCount()); +} + +TEST_F(ChatStateNotifierTest, testSendTwoMessages) { + setContactHas85Caps(); + notifier_->setUserIsTyping(); + notifier_->userSentMessage(); + notifier_->setUserIsTyping(); + notifier_->userSentMessage(); + ASSERT_EQ(2, getComposingCount()); +} + +TEST_F(ChatStateNotifierTest, testCancelledNewMessage) { + setContactHas85Caps(); + notifier_->setUserIsTyping(); + notifier_->userCancelledNewMessage(); + ASSERT_EQ(1, getComposingCount()); + ASSERT_EQ(1, getActiveCount()); + ASSERT_EQ(ChatState::Active, stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size() - 1]->getPayload<ChatState>()->getChatState()); +} + +TEST_F(ChatStateNotifierTest, testIdleWhileTypingNewMessage) { + setContactHas85Caps(); + //The channel should be empty + ASSERT_EQ(0, getComposingCount()); + ASSERT_EQ(0, getActiveCount()); + notifier_->setUserIsTyping(); + timerFactory_->setTime(1); + //1 Composing stanza is expected + ASSERT_EQ(1, getComposingCount()); + ASSERT_EQ(0, getActiveCount()); + timerFactory_->setTime(2); + //The idleTimer period has expired, the channel should have 1 composing and 1 active status stanza + ASSERT_EQ(1, getComposingCount()); + ASSERT_EQ(1, getActiveCount()); + ASSERT_EQ(ChatState::Active, stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size() - 1]->getPayload<ChatState>()->getChatState()); + timerFactory_->setTime(4); + //At the second tick no further state stanzas should be sent. + ASSERT_EQ(1, getComposingCount()); + ASSERT_EQ(1, getActiveCount()); + ASSERT_EQ(ChatState::Active, stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size() - 1]->getPayload<ChatState>()->getChatState()); +} + +TEST_F(ChatStateNotifierTest, testIdleWhileTypingNewMessageNoCaps) { + notifier_->setUserIsTyping(); + timerFactory_->setTime(3); + ASSERT_EQ(0, getComposingCount()); + ASSERT_EQ(0, getActiveCount()); +} +TEST_F(ChatStateNotifierTest, testContactShouldReceiveStates_CapsOnly) { + setContactHas85Caps(); + std::shared_ptr<Message> message(new Message()); + notifier_->addChatStateRequest(message); + EXPECT_TRUE(message->getPayload<ChatState>()); + ASSERT_EQ(ChatState::Active, message->getPayload<ChatState>()->getChatState()); +} + +TEST_F(ChatStateNotifierTest, testContactShouldReceiveStates_CapsNorActive) { + std::shared_ptr<Message> message(new Message()); + notifier_->addChatStateRequest(message); + EXPECT_TRUE(!message->getPayload<ChatState>()); +} + +TEST_F(ChatStateNotifierTest, testContactShouldReceiveStates_ActiveOverrideOn) { + notifier_->receivedMessageFromContact(true); + std::shared_ptr<Message> message(new Message()); + notifier_->addChatStateRequest(message); + EXPECT_TRUE(message->getPayload<ChatState>()); + ASSERT_EQ(ChatState::Active, message->getPayload<ChatState>()->getChatState()); +} + +TEST_F(ChatStateNotifierTest, testContactShouldReceiveStates_ActiveOverrideOff) { + setContactHas85Caps(); + notifier_->receivedMessageFromContact(false); + /* I originally read the MUST NOT send after receiving without Active and + * thought this should check for false, but I later found it was OPTIONAL + * (MAY) behaviour only for if you didn't receive caps. + */ + std::shared_ptr<Message> message(new Message()); + notifier_->addChatStateRequest(message); + EXPECT_TRUE(message->getPayload<ChatState>()); + ASSERT_EQ(ChatState::Active, message->getPayload<ChatState>()->getChatState()); +} + + +TEST_F(ChatStateNotifierTest, testStartTypingReply_CapsIncluded) { + setContactHas85Caps(); + notifier_->setUserIsTyping(); + ASSERT_EQ(1, getComposingCount()); +} + +TEST_F(ChatStateNotifierTest, testContinueTypingReply_CapsIncluded) { + setContactHas85Caps(); + notifier_->setUserIsTyping(); + notifier_->setUserIsTyping(); + notifier_->setUserIsTyping(); + ASSERT_EQ(1, getComposingCount()); + notifier_->userSentMessage(); + notifier_->setUserIsTyping(); + ASSERT_EQ(2, getComposingCount()); + +} + +TEST_F(ChatStateNotifierTest, testTypeReplies_WentOffline) { + setContactHas85Caps(); + notifier_->setUserIsTyping(); + ASSERT_EQ(1, getComposingCount()); + notifier_->setContactIsOnline(false); + notifier_->userSentMessage(); + notifier_->setUserIsTyping(); + ASSERT_EQ(1, getComposingCount()); +} |