From bc12c1db41d495f65a97e686f71e6254dd9c5344 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Mon, 15 May 2023 16:51:59 -0400 Subject: [PATCH 01/19] dev: add IEmojis interface for Emojis --- src/providers/emoji/Emojis.cpp | 14 ++++++++++++-- src/providers/emoji/Emojis.hpp | 22 +++++++++++++++++++--- src/singletons/Emotes.hpp | 6 ++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/providers/emoji/Emojis.cpp b/src/providers/emoji/Emojis.cpp index 72c42983742..7872d6e6827 100644 --- a/src/providers/emoji/Emojis.cpp +++ b/src/providers/emoji/Emojis.cpp @@ -265,7 +265,7 @@ void Emojis::loadEmojiSet() } std::vector> Emojis::parse( - const QString &text) + const QString &text) const { auto result = std::vector>(); int lastParsedEmojiEndIndex = 0; @@ -359,7 +359,7 @@ std::vector> Emojis::parse( return result; } -QString Emojis::replaceShortCodes(const QString &text) +QString Emojis::replaceShortCodes(const QString &text) const { QString ret(text); auto it = this->findShortCodesRegex_.globalMatch(text); @@ -393,4 +393,14 @@ QString Emojis::replaceShortCodes(const QString &text) return ret; } +const EmojiMap &Emojis::getEmojis() const +{ + return this->emojis; +} + +const std::vector &Emojis::getShortCodes() const +{ + return this->shortCodes; +} + } // namespace chatterino diff --git a/src/providers/emoji/Emojis.hpp b/src/providers/emoji/Emojis.hpp index 2f1679b6e85..217aa1f4ad3 100644 --- a/src/providers/emoji/Emojis.hpp +++ b/src/providers/emoji/Emojis.hpp @@ -37,16 +37,32 @@ struct EmojiData { using EmojiMap = ConcurrentMap>; -class Emojis +class IEmojis +{ +public: + virtual ~IEmojis() = default; + + virtual std::vector> parse( + const QString &text) const = 0; + virtual const EmojiMap &getEmojis() const = 0; + virtual const std::vector &getShortCodes() const = 0; + virtual QString replaceShortCodes(const QString &text) const = 0; +}; + +class Emojis : public IEmojis { public: void initialize(); void load(); - std::vector> parse(const QString &text); + std::vector> parse( + const QString &text) const override; EmojiMap emojis; std::vector shortCodes; - QString replaceShortCodes(const QString &text); + QString replaceShortCodes(const QString &text) const override; + + const EmojiMap &getEmojis() const override; + const std::vector &getShortCodes() const override; private: void loadEmojis(); diff --git a/src/singletons/Emotes.hpp b/src/singletons/Emotes.hpp index 1a65a17d0c7..f74dab87335 100644 --- a/src/singletons/Emotes.hpp +++ b/src/singletons/Emotes.hpp @@ -16,6 +16,7 @@ class IEmotes virtual ~IEmotes() = default; virtual ITwitchEmotes *getTwitchEmotes() = 0; + virtual IEmojis *getEmojis() = 0; }; class Emotes final : public IEmotes, public Singleton @@ -32,6 +33,11 @@ class Emotes final : public IEmotes, public Singleton return &this->twitch; } + IEmojis *getEmojis() final + { + return &this->emojis; + } + TwitchEmotes twitch; Emojis emojis; From baa8541835f73577d106364819ded8caa46f668c Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 12:52:51 -0400 Subject: [PATCH 02/19] dev: extract completion emote list into function --- src/widgets/splits/InputCompletionPopup.cpp | 155 +++++++++++--------- src/widgets/splits/InputCompletionPopup.hpp | 17 +++ 2 files changed, 99 insertions(+), 73 deletions(-) diff --git a/src/widgets/splits/InputCompletionPopup.cpp b/src/widgets/splits/InputCompletionPopup.cpp index eb6a2ace747..b370faf5da7 100644 --- a/src/widgets/splits/InputCompletionPopup.cpp +++ b/src/widgets/splits/InputCompletionPopup.cpp @@ -17,12 +17,7 @@ namespace { using namespace chatterino; - -struct CompletionEmote { - EmotePtr emote; - QString displayName; - QString providerName; -}; +using namespace chatterino::detail; void addEmotes(std::vector &out, const EmoteMap &map, const QString &text, const QString &providerName) @@ -55,97 +50,111 @@ void addEmojis(std::vector &out, const EmojiMap &map, namespace chatterino { -InputCompletionPopup::InputCompletionPopup(QWidget *parent) - : BasePopup({BasePopup::EnableCustomFrame, BasePopup::Frameless, - BasePopup::DontFocus, BaseWindow::DisableLayoutSave}, - parent) - , model_(this) -{ - this->initLayout(); - - QObject::connect(&this->redrawTimer_, &QTimer::timeout, this, [this] { - if (this->isVisible()) - { - this->ui_.listView->doItemsLayout(); - } - }); - this->redrawTimer_.setInterval(33); -} +namespace detail { -void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel) -{ - std::vector emotes; - auto *tc = dynamic_cast(channel.get()); - // returns true also for special Twitch channels (/live, /mentions, /whispers, etc.) - if (channel->isTwitchChannel()) + std::vector buildCompletionEmoteList(const QString &text, + ChannelPtr channel) { - if (auto user = getApp()->accounts->twitch.getCurrent()) + std::vector emotes; + auto *tc = dynamic_cast(channel.get()); + // returns true also for special Twitch channels (/live, /mentions, /whispers, etc.) + if (channel->isTwitchChannel()) { - // Twitch Emotes available globally - auto emoteData = user->accessEmotes(); - addEmotes(emotes, emoteData->emotes, text, "Twitch Emote"); - - // Twitch Emotes available locally - auto localEmoteData = user->accessLocalEmotes(); - if (tc && - localEmoteData->find(tc->roomId()) != localEmoteData->end()) + if (auto user = getApp()->accounts->twitch.getCurrent()) { - if (const auto *localEmotes = &localEmoteData->at(tc->roomId())) + // Twitch Emotes available globally + auto emoteData = user->accessEmotes(); + addEmotes(emotes, emoteData->emotes, text, "Twitch Emote"); + + // Twitch Emotes available locally + auto localEmoteData = user->accessLocalEmotes(); + if (tc && + localEmoteData->find(tc->roomId()) != localEmoteData->end()) { - addEmotes(emotes, *localEmotes, text, - "Local Twitch Emotes"); + if (const auto *localEmotes = + &localEmoteData->at(tc->roomId())) + { + addEmotes(emotes, *localEmotes, text, + "Local Twitch Emotes"); + } } } - } - if (tc) - { - // TODO extract "Channel {BetterTTV,7TV,FrankerFaceZ}" text into a #define. - if (auto bttv = tc->bttvEmotes()) + if (tc) { - addEmotes(emotes, *bttv, text, "Channel BetterTTV"); + // TODO extract "Channel {BetterTTV,7TV,FrankerFaceZ}" text into a #define. + if (auto bttv = tc->bttvEmotes()) + { + addEmotes(emotes, *bttv, text, "Channel BetterTTV"); + } + if (auto ffz = tc->ffzEmotes()) + { + addEmotes(emotes, *ffz, text, "Channel FrankerFaceZ"); + } + if (auto seventv = tc->seventvEmotes()) + { + addEmotes(emotes, *seventv, text, "Channel 7TV"); + } } - if (auto ffz = tc->ffzEmotes()) + + if (auto bttvG = getApp()->twitch->getBttvEmotes().emotes()) { - addEmotes(emotes, *ffz, text, "Channel FrankerFaceZ"); + addEmotes(emotes, *bttvG, text, "Global BetterTTV"); } - if (auto seventv = tc->seventvEmotes()) + if (auto ffzG = getApp()->twitch->getFfzEmotes().emotes()) { - addEmotes(emotes, *seventv, text, "Channel 7TV"); + addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ"); + } + if (auto seventvG = + getApp()->twitch->getSeventvEmotes().globalEmotes()) + { + addEmotes(emotes, *seventvG, text, "Global 7TV"); } } - if (auto bttvG = getApp()->twitch->getBttvEmotes().emotes()) - { - addEmotes(emotes, *bttvG, text, "Global BetterTTV"); - } - if (auto ffzG = getApp()->twitch->getFfzEmotes().emotes()) - { - addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ"); - } - if (auto seventvG = getApp()->twitch->getSeventvEmotes().globalEmotes()) + addEmojis(emotes, getApp()->emotes->emojis.emojis, text); + + // if there is an exact match, put that emote first + for (size_t i = 1; i < emotes.size(); i++) { - addEmotes(emotes, *seventvG, text, "Global 7TV"); + auto emoteText = emotes.at(i).displayName; + + // test for match or match with colon at start for emotes like ":)" + if (emoteText.compare(text, Qt::CaseInsensitive) == 0 || + emoteText.compare(":" + text, Qt::CaseInsensitive) == 0) + { + auto emote = emotes[i]; + emotes.erase(emotes.begin() + int(i)); + emotes.insert(emotes.begin(), emote); + break; + } } + + return emotes; } - addEmojis(emotes, getApp()->emotes->emojis.emojis, text); +} // namespace detail - // if there is an exact match, put that emote first - for (size_t i = 1; i < emotes.size(); i++) - { - auto emoteText = emotes.at(i).displayName; +InputCompletionPopup::InputCompletionPopup(QWidget *parent) + : BasePopup({BasePopup::EnableCustomFrame, BasePopup::Frameless, + BasePopup::DontFocus, BaseWindow::DisableLayoutSave}, + parent) + , model_(this) +{ + this->initLayout(); - // test for match or match with colon at start for emotes like ":)" - if (emoteText.compare(text, Qt::CaseInsensitive) == 0 || - emoteText.compare(":" + text, Qt::CaseInsensitive) == 0) + QObject::connect(&this->redrawTimer_, &QTimer::timeout, this, [this] { + if (this->isVisible()) { - auto emote = emotes[i]; - emotes.erase(emotes.begin() + int(i)); - emotes.insert(emotes.begin(), emote); - break; + this->ui_.listView->doItemsLayout(); } - } + }); + this->redrawTimer_.setInterval(33); +} + +void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel) +{ + auto emotes = detail::buildCompletionEmoteList(text, std::move(channel)); this->model_.clear(); diff --git a/src/widgets/splits/InputCompletionPopup.hpp b/src/widgets/splits/InputCompletionPopup.hpp index a75b6456355..9f36bb5ae76 100644 --- a/src/widgets/splits/InputCompletionPopup.hpp +++ b/src/widgets/splits/InputCompletionPopup.hpp @@ -5,12 +5,29 @@ #include #include +#include namespace chatterino { class Channel; using ChannelPtr = std::shared_ptr; +struct Emote; +using EmotePtr = std::shared_ptr; + +namespace detail { + + struct CompletionEmote { + EmotePtr emote; + QString displayName; + QString providerName; + }; + + std::vector buildCompletionEmoteList(const QString &text, + ChannelPtr channel); + +} // namespace detail + class GenericListView; class InputCompletionPopup : public BasePopup From 9d83aac3f6591e9758a5ff0c516c34bc1e4e4324 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 12:58:25 -0400 Subject: [PATCH 03/19] dev: use getIApp for building emote list --- src/widgets/splits/InputCompletionPopup.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/widgets/splits/InputCompletionPopup.cpp b/src/widgets/splits/InputCompletionPopup.cpp index b370faf5da7..886b6ecbc50 100644 --- a/src/widgets/splits/InputCompletionPopup.cpp +++ b/src/widgets/splits/InputCompletionPopup.cpp @@ -56,11 +56,12 @@ namespace detail { ChannelPtr channel) { std::vector emotes; + auto *app = getIApp(); auto *tc = dynamic_cast(channel.get()); // returns true also for special Twitch channels (/live, /mentions, /whispers, etc.) if (channel->isTwitchChannel()) { - if (auto user = getApp()->accounts->twitch.getCurrent()) + if (auto user = app->getAccounts()->twitch.getCurrent()) { // Twitch Emotes available globally auto emoteData = user->accessEmotes(); @@ -97,22 +98,22 @@ namespace detail { } } - if (auto bttvG = getApp()->twitch->getBttvEmotes().emotes()) + if (auto bttvG = app->getTwitch()->getBttvEmotes().emotes()) { addEmotes(emotes, *bttvG, text, "Global BetterTTV"); } - if (auto ffzG = getApp()->twitch->getFfzEmotes().emotes()) + if (auto ffzG = app->getTwitch()->getFfzEmotes().emotes()) { addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ"); } if (auto seventvG = - getApp()->twitch->getSeventvEmotes().globalEmotes()) + app->getTwitch()->getSeventvEmotes().globalEmotes()) { addEmotes(emotes, *seventvG, text, "Global 7TV"); } } - addEmojis(emotes, getApp()->emotes->emojis.emojis, text); + addEmojis(emotes, app->getEmotes()->getEmojis()->getEmojis(), text); // if there is an exact match, put that emote first for (size_t i = 1; i < emotes.size(); i++) From 3ca01334f7ca3bcb69dff5bbee7c77e69887ea03 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 13:21:31 -0400 Subject: [PATCH 04/19] dev: add ITwitchIrcServer interface --- benchmarks/src/Highlights.cpp | 4 ++-- src/Application.cpp | 5 +++++ src/Application.hpp | 8 +++----- src/providers/twitch/TwitchIrcServer.hpp | 22 ++++++++++++++++++---- tests/src/HighlightController.cpp | 2 +- tests/src/TwitchMessageBuilder.cpp | 2 +- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/benchmarks/src/Highlights.cpp b/benchmarks/src/Highlights.cpp index c35a0847ffe..319df86da4c 100644 --- a/benchmarks/src/Highlights.cpp +++ b/benchmarks/src/Highlights.cpp @@ -1,11 +1,11 @@ #include "Application.hpp" -#include "singletons/Settings.hpp" #include "common/Channel.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/highlights/HighlightController.hpp" #include "controllers/highlights/HighlightPhrase.hpp" #include "messages/Message.hpp" #include "messages/SharedMessageBuilder.hpp" +#include "singletons/Settings.hpp" #include "util/Helpers.hpp" #include @@ -88,7 +88,7 @@ class MockApplication : IApplication { return &this->highlights; } - TwitchIrcServer *getTwitch() override + ITwitchIrcServer *getTwitch() override { return nullptr; } diff --git a/src/Application.cpp b/src/Application.cpp index b794a79667f..248cb0d86d9 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -245,6 +245,11 @@ IUserDataController *Application::getUserData() return this->userData; } +ITwitchIrcServer *Application::getTwitch() +{ + return this->twitch; +} + void Application::save() { for (auto &singleton : this->singletons_) diff --git a/src/Application.hpp b/src/Application.hpp index 7c55255055c..27f1c2f60fb 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -10,6 +10,7 @@ namespace chatterino { class TwitchIrcServer; +class ITwitchIrcServer; class PubSub; class CommandController; @@ -55,7 +56,7 @@ class IApplication virtual CommandController *getCommands() = 0; virtual HighlightController *getHighlights() = 0; virtual NotificationController *getNotifications() = 0; - virtual TwitchIrcServer *getTwitch() = 0; + virtual ITwitchIrcServer *getTwitch() = 0; virtual ChatterinoBadges *getChatterinoBadges() = 0; virtual FfzBadges *getFfzBadges() = 0; virtual IUserDataController *getUserData() = 0; @@ -141,10 +142,7 @@ class Application : public IApplication { return this->highlights; } - TwitchIrcServer *getTwitch() override - { - return this->twitch; - } + ITwitchIrcServer *getTwitch() override; ChatterinoBadges *getChatterinoBadges() override { return this->chatterinoBadges; diff --git a/src/providers/twitch/TwitchIrcServer.hpp b/src/providers/twitch/TwitchIrcServer.hpp index 9a9a2280076..4593c48ba0b 100644 --- a/src/providers/twitch/TwitchIrcServer.hpp +++ b/src/providers/twitch/TwitchIrcServer.hpp @@ -23,7 +23,21 @@ class TwitchChannel; class BttvLiveUpdates; class SeventvEventAPI; -class TwitchIrcServer final : public AbstractIrcServer, public Singleton +class ITwitchIrcServer +{ +public: + virtual ~ITwitchIrcServer() = default; + + virtual const BttvEmotes &getBttvEmotes() const = 0; + virtual const FfzEmotes &getFfzEmotes() const = 0; + virtual const SeventvEmotes &getSeventvEmotes() const = 0; + + // Update this interface with TwitchIrcServer methods as needed +}; + +class TwitchIrcServer final : public AbstractIrcServer, + public Singleton, + public ITwitchIrcServer { public: TwitchIrcServer(); @@ -70,9 +84,9 @@ class TwitchIrcServer final : public AbstractIrcServer, public Singleton std::unique_ptr bttvLiveUpdates; std::unique_ptr seventvEventAPI; - const BttvEmotes &getBttvEmotes() const; - const FfzEmotes &getFfzEmotes() const; - const SeventvEmotes &getSeventvEmotes() const; + const BttvEmotes &getBttvEmotes() const override; + const FfzEmotes &getFfzEmotes() const override; + const SeventvEmotes &getSeventvEmotes() const override; protected: virtual void initializeConnection(IrcConnection *connection, diff --git a/tests/src/HighlightController.cpp b/tests/src/HighlightController.cpp index f5609ed45bf..8a532e7bc95 100644 --- a/tests/src/HighlightController.cpp +++ b/tests/src/HighlightController.cpp @@ -67,7 +67,7 @@ class MockApplication : IApplication { return &this->highlights; } - TwitchIrcServer *getTwitch() override + ITwitchIrcServer *getTwitch() override { return nullptr; } diff --git a/tests/src/TwitchMessageBuilder.cpp b/tests/src/TwitchMessageBuilder.cpp index b1ce38eb931..79962f11133 100644 --- a/tests/src/TwitchMessageBuilder.cpp +++ b/tests/src/TwitchMessageBuilder.cpp @@ -62,7 +62,7 @@ class MockApplication : IApplication { return nullptr; } - TwitchIrcServer *getTwitch() override + ITwitchIrcServer *getTwitch() override { return nullptr; } From fa3cbd5dc9a2e3d158887cc684a28478ac5ecee1 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 14:24:00 -0400 Subject: [PATCH 05/19] dev: extract MockHelix to own file --- src/providers/twitch/api/README.md | 2 +- tests/src/HighlightController.cpp | 356 +--------------------------- tests/src/mocks/Helix.hpp | 368 +++++++++++++++++++++++++++++ 3 files changed, 370 insertions(+), 356 deletions(-) create mode 100644 tests/src/mocks/Helix.hpp diff --git a/src/providers/twitch/api/README.md b/src/providers/twitch/api/README.md index 173292ca6ff..00b00d289e4 100644 --- a/src/providers/twitch/api/README.md +++ b/src/providers/twitch/api/README.md @@ -12,7 +12,7 @@ If you're adding support for a new endpoint, these are the things you should kno 1. Add a virtual function in the `IHelix` class. Naming should reflect the API name as best as possible. 1. Override the virtual function in the `Helix` class. -1. Mock the function in the `MockHelix` class in the `tests/src/HighlightController.cpp` file. +1. Mock the function in the `MockHelix` class in the `tests/src/mocks/Helix.cpp` file. 1. (Optional) Make a new error enum for the failure callback. For a simple example, see the `updateUserChatColor` function and its error enum `HelixUpdateUserChatColorError`. diff --git a/tests/src/HighlightController.cpp b/tests/src/HighlightController.cpp index 8a532e7bc95..eb3b3be05b1 100644 --- a/tests/src/HighlightController.cpp +++ b/tests/src/HighlightController.cpp @@ -5,6 +5,7 @@ #include "controllers/accounts/AccountController.hpp" #include "controllers/highlights/HighlightPhrase.hpp" #include "messages/MessageBuilder.hpp" // for MessageParseArgs +#include "mocks/Helix.hpp" #include "mocks/UserData.hpp" #include "providers/twitch/api/Helix.hpp" #include "providers/twitch/TwitchBadge.hpp" // for Badge @@ -92,361 +93,6 @@ class MockApplication : IApplication } // namespace -class MockHelix : public IHelix -{ -public: - MOCK_METHOD(void, fetchUsers, - (QStringList userIds, QStringList userLogins, - ResultCallback> successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, getUserByName, - (QString userName, ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - MOCK_METHOD(void, getUserById, - (QString userId, ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, fetchUsersFollows, - (QString fromId, QString toId, - ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, getUserFollowers, - (QString userId, - ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, fetchStreams, - (QStringList userIds, QStringList userLogins, - ResultCallback> successCallback, - HelixFailureCallback failureCallback, - std::function finallyCallback), - (override)); - - MOCK_METHOD(void, getStreamById, - (QString userId, - (ResultCallback successCallback), - HelixFailureCallback failureCallback, - std::function finallyCallback), - (override)); - - MOCK_METHOD(void, getStreamByName, - (QString userName, - (ResultCallback successCallback), - HelixFailureCallback failureCallback, - std::function finallyCallback), - (override)); - - MOCK_METHOD(void, fetchGames, - (QStringList gameIds, QStringList gameNames, - (ResultCallback> successCallback), - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, searchGames, - (QString gameName, - ResultCallback> successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, getGameById, - (QString gameId, ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, createClip, - (QString channelId, ResultCallback successCallback, - std::function failureCallback, - std::function finallyCallback), - (override)); - - MOCK_METHOD(void, getChannel, - (QString broadcasterId, - ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, createStreamMarker, - (QString broadcasterId, QString description, - ResultCallback successCallback, - std::function failureCallback), - (override)); - - MOCK_METHOD(void, loadBlocks, - (QString userId, - ResultCallback> successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, blockUser, - (QString targetUserId, std::function successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, unblockUser, - (QString targetUserId, std::function successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, updateChannel, - (QString broadcasterId, QString gameId, QString language, - QString title, - std::function successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, manageAutoModMessages, - (QString userID, QString msgID, QString action, - std::function successCallback, - std::function failureCallback), - (override)); - - MOCK_METHOD(void, getCheermotes, - (QString broadcasterId, - ResultCallback> successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, getEmoteSetData, - (QString emoteSetId, - ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, getChannelEmotes, - (QString broadcasterId, - ResultCallback> successCallback, - HelixFailureCallback failureCallback), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( - void, getGlobalBadges, - (ResultCallback successCallback, - (FailureCallback failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, getChannelBadges, - (QString broadcasterID, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateUserChatColor, - (QString userID, QString color, - ResultCallback<> successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, deleteChatMessages, - (QString broadcasterID, QString moderatorID, QString messageID, - ResultCallback<> successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, addChannelModerator, - (QString broadcasterID, QString userID, - ResultCallback<> successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, removeChannelModerator, - (QString broadcasterID, QString userID, - ResultCallback<> successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, sendChatAnnouncement, - (QString broadcasterID, QString moderatorID, QString message, - HelixAnnouncementColor color, ResultCallback<> successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( - void, addChannelVIP, - (QString broadcasterID, QString userID, - ResultCallback<> successCallback, - (FailureCallback failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, removeChannelVIP, - (QString broadcasterID, QString userID, - ResultCallback<> successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( - void, unbanUser, - (QString broadcasterID, QString moderatorID, QString userID, - ResultCallback<> successCallback, - (FailureCallback failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( // /raid - void, startRaid, - (QString fromBroadcasterID, QString toBroadcasterId, - ResultCallback<> successCallback, - (FailureCallback failureCallback)), - (override)); // /raid - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( // /unraid - void, cancelRaid, - (QString broadcasterID, ResultCallback<> successCallback, - (FailureCallback failureCallback)), - (override)); // /unraid - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateEmoteMode, - (QString broadcasterID, QString moderatorID, bool emoteMode, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateFollowerMode, - (QString broadcasterID, QString moderatorID, - boost::optional followerModeDuration, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateNonModeratorChatDelay, - (QString broadcasterID, QString moderatorID, - boost::optional nonModeratorChatDelayDuration, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateSlowMode, - (QString broadcasterID, QString moderatorID, - boost::optional slowModeWaitTime, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateSubscriberMode, - (QString broadcasterID, QString moderatorID, - bool subscriberMode, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateUniqueChatMode, - (QString broadcasterID, QString moderatorID, - bool uniqueChatMode, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - // update chat settings - - // /timeout, /ban - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, banUser, - (QString broadcasterID, QString moderatorID, QString userID, - boost::optional duration, QString reason, - ResultCallback<> successCallback, - (FailureCallback failureCallback)), - (override)); // /timeout, /ban - - // /w - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, sendWhisper, - (QString fromUserID, QString toUserID, QString message, - ResultCallback<> successCallback, - (FailureCallback failureCallback)), - (override)); // /w - - // getChatters - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( - void, getChatters, - (QString broadcasterID, QString moderatorID, int maxChattersToFetch, - ResultCallback successCallback, - (FailureCallback failureCallback)), - (override)); // getChatters - - // /vips - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( - void, getChannelVIPs, - (QString broadcasterID, - ResultCallback> successCallback, - (FailureCallback failureCallback)), - (override)); // /vips - - // /commercial - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( - void, startCommercial, - (QString broadcasterID, int length, - ResultCallback successCallback, - (FailureCallback failureCallback)), - (override)); // /commercial - - // /mods - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD( - void, getModerators, - (QString broadcasterID, int maxModeratorsToFetch, - ResultCallback> successCallback, - (FailureCallback failureCallback)), - (override)); // /mods - - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateShieldMode, - (QString broadcasterID, QString moderatorID, bool isActive, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); - - MOCK_METHOD(void, update, (QString clientId, QString oauthToken), - (override)); - -protected: - // The extra parenthesis around the failure callback is because its type contains a comma - MOCK_METHOD(void, updateChatSettings, - (QString broadcasterID, QString moderatorID, QJsonObject json, - ResultCallback successCallback, - (FailureCallback - failureCallback)), - (override)); -}; - static QString DEFAULT_SETTINGS = R"!( { "accounts": { diff --git a/tests/src/mocks/Helix.hpp b/tests/src/mocks/Helix.hpp new file mode 100644 index 00000000000..8684b07567f --- /dev/null +++ b/tests/src/mocks/Helix.hpp @@ -0,0 +1,368 @@ +#pragma once + +#include "providers/twitch/api/Helix.hpp" + +#include +#include +#include + +#include + +using namespace chatterino; + +class MockHelix : public IHelix +{ +public: + virtual ~MockHelix() = default; + + MOCK_METHOD(void, fetchUsers, + (QStringList userIds, QStringList userLogins, + ResultCallback> successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, getUserByName, + (QString userName, ResultCallback successCallback, + HelixFailureCallback failureCallback), + (override)); + MOCK_METHOD(void, getUserById, + (QString userId, ResultCallback successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, fetchUsersFollows, + (QString fromId, QString toId, + ResultCallback successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, getUserFollowers, + (QString userId, + ResultCallback successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, fetchStreams, + (QStringList userIds, QStringList userLogins, + ResultCallback> successCallback, + HelixFailureCallback failureCallback, + std::function finallyCallback), + (override)); + + MOCK_METHOD(void, getStreamById, + (QString userId, + (ResultCallback successCallback), + HelixFailureCallback failureCallback, + std::function finallyCallback), + (override)); + + MOCK_METHOD(void, getStreamByName, + (QString userName, + (ResultCallback successCallback), + HelixFailureCallback failureCallback, + std::function finallyCallback), + (override)); + + MOCK_METHOD(void, fetchGames, + (QStringList gameIds, QStringList gameNames, + (ResultCallback> successCallback), + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, searchGames, + (QString gameName, + ResultCallback> successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, getGameById, + (QString gameId, ResultCallback successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, createClip, + (QString channelId, ResultCallback successCallback, + std::function failureCallback, + std::function finallyCallback), + (override)); + + MOCK_METHOD(void, getChannel, + (QString broadcasterId, + ResultCallback successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, createStreamMarker, + (QString broadcasterId, QString description, + ResultCallback successCallback, + std::function failureCallback), + (override)); + + MOCK_METHOD(void, loadBlocks, + (QString userId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, blockUser, + (QString targetUserId, std::function successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, unblockUser, + (QString targetUserId, std::function successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, updateChannel, + (QString broadcasterId, QString gameId, QString language, + QString title, + std::function successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, manageAutoModMessages, + (QString userID, QString msgID, QString action, + std::function successCallback, + std::function failureCallback), + (override)); + + MOCK_METHOD(void, getCheermotes, + (QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, getEmoteSetData, + (QString emoteSetId, + ResultCallback successCallback, + HelixFailureCallback failureCallback), + (override)); + + MOCK_METHOD(void, getChannelEmotes, + (QString broadcasterId, + ResultCallback> successCallback, + HelixFailureCallback failureCallback), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( + void, getGlobalBadges, + (ResultCallback successCallback, + (FailureCallback failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, getChannelBadges, + (QString broadcasterID, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateUserChatColor, + (QString userID, QString color, + ResultCallback<> successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, deleteChatMessages, + (QString broadcasterID, QString moderatorID, QString messageID, + ResultCallback<> successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, addChannelModerator, + (QString broadcasterID, QString userID, + ResultCallback<> successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, removeChannelModerator, + (QString broadcasterID, QString userID, + ResultCallback<> successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, sendChatAnnouncement, + (QString broadcasterID, QString moderatorID, QString message, + HelixAnnouncementColor color, ResultCallback<> successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( + void, addChannelVIP, + (QString broadcasterID, QString userID, + ResultCallback<> successCallback, + (FailureCallback failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, removeChannelVIP, + (QString broadcasterID, QString userID, + ResultCallback<> successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( + void, unbanUser, + (QString broadcasterID, QString moderatorID, QString userID, + ResultCallback<> successCallback, + (FailureCallback failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( // /raid + void, startRaid, + (QString fromBroadcasterID, QString toBroadcasterId, + ResultCallback<> successCallback, + (FailureCallback failureCallback)), + (override)); // /raid + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( // /unraid + void, cancelRaid, + (QString broadcasterID, ResultCallback<> successCallback, + (FailureCallback failureCallback)), + (override)); // /unraid + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateEmoteMode, + (QString broadcasterID, QString moderatorID, bool emoteMode, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateFollowerMode, + (QString broadcasterID, QString moderatorID, + boost::optional followerModeDuration, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateNonModeratorChatDelay, + (QString broadcasterID, QString moderatorID, + boost::optional nonModeratorChatDelayDuration, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateSlowMode, + (QString broadcasterID, QString moderatorID, + boost::optional slowModeWaitTime, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateSubscriberMode, + (QString broadcasterID, QString moderatorID, + bool subscriberMode, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateUniqueChatMode, + (QString broadcasterID, QString moderatorID, + bool uniqueChatMode, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + // update chat settings + + // /timeout, /ban + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, banUser, + (QString broadcasterID, QString moderatorID, QString userID, + boost::optional duration, QString reason, + ResultCallback<> successCallback, + (FailureCallback failureCallback)), + (override)); // /timeout, /ban + + // /w + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, sendWhisper, + (QString fromUserID, QString toUserID, QString message, + ResultCallback<> successCallback, + (FailureCallback failureCallback)), + (override)); // /w + + // getChatters + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( + void, getChatters, + (QString broadcasterID, QString moderatorID, int maxChattersToFetch, + ResultCallback successCallback, + (FailureCallback failureCallback)), + (override)); // getChatters + + // /vips + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( + void, getChannelVIPs, + (QString broadcasterID, + ResultCallback> successCallback, + (FailureCallback failureCallback)), + (override)); // /vips + + // /commercial + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( + void, startCommercial, + (QString broadcasterID, int length, + ResultCallback successCallback, + (FailureCallback failureCallback)), + (override)); // /commercial + + // /mods + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD( + void, getModerators, + (QString broadcasterID, int maxModeratorsToFetch, + ResultCallback> successCallback, + (FailureCallback failureCallback)), + (override)); // /mods + + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateShieldMode, + (QString broadcasterID, QString moderatorID, bool isActive, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); + + MOCK_METHOD(void, update, (QString clientId, QString oauthToken), + (override)); + +protected: + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, updateChatSettings, + (QString broadcasterID, QString moderatorID, QJsonObject json, + ResultCallback successCallback, + (FailureCallback + failureCallback)), + (override)); +}; From 34d1d5288684b1c5cb214a5b4ced68c5bd923e51 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 15:10:22 -0400 Subject: [PATCH 06/19] dev: move empty application interface to EmptyApplication --- tests/src/HighlightController.cpp | 50 ++------------------- tests/src/TwitchMessageBuilder.cpp | 53 ++-------------------- tests/src/mocks/EmptyApplication.hpp | 66 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 96 deletions(-) create mode 100644 tests/src/mocks/EmptyApplication.hpp diff --git a/tests/src/HighlightController.cpp b/tests/src/HighlightController.cpp index eb3b3be05b1..b6c302c61cf 100644 --- a/tests/src/HighlightController.cpp +++ b/tests/src/HighlightController.cpp @@ -1,10 +1,10 @@ #include "controllers/highlights/HighlightController.hpp" -#include "Application.hpp" #include "BaseSettings.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/highlights/HighlightPhrase.hpp" #include "messages/MessageBuilder.hpp" // for MessageParseArgs +#include "mocks/EmptyApplication.hpp" #include "mocks/Helix.hpp" #include "mocks/UserData.hpp" #include "providers/twitch/api/Helix.hpp" @@ -25,61 +25,19 @@ using ::testing::Exactly; namespace { -class MockApplication : IApplication +class MockApplication : EmptyApplication { public: - Theme *getThemes() override - { - return nullptr; - } - Fonts *getFonts() override - { - return nullptr; - } - IEmotes *getEmotes() override - { - return nullptr; - } AccountController *getAccounts() override { return &this->accounts; } - HotkeyController *getHotkeys() override - { - return nullptr; - } - WindowManager *getWindows() override - { - return nullptr; - } - Toasts *getToasts() override - { - return nullptr; - } - CommandController *getCommands() override - { - return nullptr; - } - NotificationController *getNotifications() override - { - return nullptr; - } + HighlightController *getHighlights() override { return &this->highlights; } - ITwitchIrcServer *getTwitch() override - { - return nullptr; - } - ChatterinoBadges *getChatterinoBadges() override - { - return nullptr; - } - FfzBadges *getFfzBadges() override - { - return nullptr; - } + IUserDataController *getUserData() override { return &this->userData; diff --git a/tests/src/TwitchMessageBuilder.cpp b/tests/src/TwitchMessageBuilder.cpp index 79962f11133..c3a9981012b 100644 --- a/tests/src/TwitchMessageBuilder.cpp +++ b/tests/src/TwitchMessageBuilder.cpp @@ -1,8 +1,8 @@ #include "providers/twitch/TwitchMessageBuilder.hpp" -#include "Application.hpp" #include "common/Channel.hpp" #include "messages/MessageBuilder.hpp" +#include "mocks/EmptyApplication.hpp" #include "mocks/UserData.hpp" #include "providers/twitch/TwitchBadge.hpp" #include "singletons/Emotes.hpp" @@ -19,61 +19,14 @@ using namespace chatterino; namespace { -class MockApplication : IApplication +class MockApplication : EmptyApplication { public: - Theme *getThemes() override - { - return nullptr; - } - Fonts *getFonts() override - { - return nullptr; - } IEmotes *getEmotes() override { return &this->emotes; } - AccountController *getAccounts() override - { - return nullptr; - } - HotkeyController *getHotkeys() override - { - return nullptr; - } - WindowManager *getWindows() override - { - return nullptr; - } - Toasts *getToasts() override - { - return nullptr; - } - CommandController *getCommands() override - { - return nullptr; - } - NotificationController *getNotifications() override - { - return nullptr; - } - HighlightController *getHighlights() override - { - return nullptr; - } - ITwitchIrcServer *getTwitch() override - { - return nullptr; - } - ChatterinoBadges *getChatterinoBadges() override - { - return nullptr; - } - FfzBadges *getFfzBadges() override - { - return nullptr; - } + IUserDataController *getUserData() override { return &this->userData; diff --git a/tests/src/mocks/EmptyApplication.hpp b/tests/src/mocks/EmptyApplication.hpp new file mode 100644 index 00000000000..70195e99c9c --- /dev/null +++ b/tests/src/mocks/EmptyApplication.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "Application.hpp" + +using namespace chatterino; + +class EmptyApplication : public IApplication +{ +public: + virtual Theme *getThemes() override + { + return nullptr; + } + virtual Fonts *getFonts() override + { + return nullptr; + } + virtual IEmotes *getEmotes() override + { + return nullptr; + } + virtual AccountController *getAccounts() override + { + return nullptr; + } + virtual HotkeyController *getHotkeys() override + { + return nullptr; + } + virtual WindowManager *getWindows() override + { + return nullptr; + } + virtual Toasts *getToasts() override + { + return nullptr; + } + virtual CommandController *getCommands() override + { + return nullptr; + } + virtual NotificationController *getNotifications() override + { + return nullptr; + } + virtual HighlightController *getHighlights() override + { + return nullptr; + } + virtual ITwitchIrcServer *getTwitch() override + { + return nullptr; + } + virtual ChatterinoBadges *getChatterinoBadges() override + { + return nullptr; + } + virtual FfzBadges *getFfzBadges() override + { + return nullptr; + } + virtual IUserDataController *getUserData() override + { + return nullptr; + } +}; From 60100381e685dcf299be69e166ef3a249df97fd5 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 16:00:41 -0400 Subject: [PATCH 07/19] dev: add setEmotes method to emote providers --- src/providers/bttv/BttvEmotes.cpp | 9 +++++++-- src/providers/bttv/BttvEmotes.hpp | 1 + src/providers/ffz/FfzEmotes.cpp | 9 +++++++-- src/providers/ffz/FfzEmotes.hpp | 1 + src/providers/seventv/SeventvEmotes.cpp | 9 +++++++-- src/providers/seventv/SeventvEmotes.hpp | 1 + 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/providers/bttv/BttvEmotes.cpp b/src/providers/bttv/BttvEmotes.cpp index f214d4177f9..b1b6fb2e772 100644 --- a/src/providers/bttv/BttvEmotes.cpp +++ b/src/providers/bttv/BttvEmotes.cpp @@ -193,7 +193,7 @@ void BttvEmotes::loadEmotes() { if (!Settings::instance().enableBTTVGlobalEmotes) { - this->global_.set(EMPTY_EMOTE_MAP); + this->setEmotes(EMPTY_EMOTE_MAP); return; } @@ -203,13 +203,18 @@ void BttvEmotes::loadEmotes() auto emotes = this->global_.get(); auto pair = parseGlobalEmotes(result.parseJsonArray(), *emotes); if (pair.first) - this->global_.set( + this->setEmotes( std::make_shared(std::move(pair.second))); return pair.first; }) .execute(); } +void BttvEmotes::setEmotes(std::shared_ptr emotes) +{ + this->global_.set(std::move(emotes)); +} + void BttvEmotes::loadChannel(std::weak_ptr channel, const QString &channelId, const QString &channelDisplayName, diff --git a/src/providers/bttv/BttvEmotes.hpp b/src/providers/bttv/BttvEmotes.hpp index bca2d4b656f..bbdcacccbca 100644 --- a/src/providers/bttv/BttvEmotes.hpp +++ b/src/providers/bttv/BttvEmotes.hpp @@ -29,6 +29,7 @@ class BttvEmotes final std::shared_ptr emotes() const; boost::optional emote(const EmoteName &name) const; void loadEmotes(); + void setEmotes(std::shared_ptr emotes); static void loadChannel(std::weak_ptr channel, const QString &channelId, const QString &channelDisplayName, diff --git a/src/providers/ffz/FfzEmotes.cpp b/src/providers/ffz/FfzEmotes.cpp index 180628545dc..73be0b4dda4 100644 --- a/src/providers/ffz/FfzEmotes.cpp +++ b/src/providers/ffz/FfzEmotes.cpp @@ -188,7 +188,7 @@ void FfzEmotes::loadEmotes() { if (!Settings::instance().enableFFZGlobalEmotes) { - this->global_.set(EMPTY_EMOTE_MAP); + this->setEmotes(EMPTY_EMOTE_MAP); return; } @@ -199,13 +199,18 @@ void FfzEmotes::loadEmotes() .timeout(30000) .onSuccess([this](auto result) -> Outcome { auto parsedSet = parseGlobalEmotes(result.parseJson()); - this->global_.set(std::make_shared(std::move(parsedSet))); + this->setEmotes(std::make_shared(std::move(parsedSet))); return Success; }) .execute(); } +void FfzEmotes::setEmotes(std::shared_ptr emotes) +{ + this->global_.set(std::move(emotes)); +} + void FfzEmotes::loadChannel( std::weak_ptr channel, const QString &channelID, std::function emoteCallback, diff --git a/src/providers/ffz/FfzEmotes.hpp b/src/providers/ffz/FfzEmotes.hpp index be0726f045d..e2865fcb58d 100644 --- a/src/providers/ffz/FfzEmotes.hpp +++ b/src/providers/ffz/FfzEmotes.hpp @@ -22,6 +22,7 @@ class FfzEmotes final std::shared_ptr emotes() const; boost::optional emote(const EmoteName &name) const; void loadEmotes(); + void setEmotes(std::shared_ptr emotes); static void loadChannel( std::weak_ptr channel, const QString &channelId, std::function emoteCallback, diff --git a/src/providers/seventv/SeventvEmotes.cpp b/src/providers/seventv/SeventvEmotes.cpp index 2f7883abc65..864d8411955 100644 --- a/src/providers/seventv/SeventvEmotes.cpp +++ b/src/providers/seventv/SeventvEmotes.cpp @@ -275,7 +275,7 @@ void SeventvEmotes::loadGlobalEmotes() { if (!Settings::instance().enableSevenTVGlobalEmotes) { - this->global_.set(EMPTY_EMOTE_MAP); + this->setEmotes(EMPTY_EMOTE_MAP); return; } @@ -289,7 +289,7 @@ void SeventvEmotes::loadGlobalEmotes() auto emoteMap = parseEmotes(parsedEmotes, true); qCDebug(chatterinoSeventv) << "Loaded" << emoteMap.size() << "7TV Global Emotes"; - this->global_.set(std::make_shared(std::move(emoteMap))); + this->setEmotes(std::make_shared(std::move(emoteMap))); return Success; }) @@ -300,6 +300,11 @@ void SeventvEmotes::loadGlobalEmotes() .execute(); } +void SeventvEmotes::setEmotes(std::shared_ptr emotes) +{ + this->global_.set(std::move(emotes)); +} + void SeventvEmotes::loadChannelEmotes( const std::weak_ptr &channel, const QString &channelId, std::function callback, bool manualRefresh) diff --git a/src/providers/seventv/SeventvEmotes.hpp b/src/providers/seventv/SeventvEmotes.hpp index f978337be43..ea21ff7e1b7 100644 --- a/src/providers/seventv/SeventvEmotes.hpp +++ b/src/providers/seventv/SeventvEmotes.hpp @@ -75,6 +75,7 @@ class SeventvEmotes final std::shared_ptr globalEmotes() const; boost::optional globalEmote(const EmoteName &name) const; void loadGlobalEmotes(); + void setEmotes(std::shared_ptr emotes); static void loadChannelEmotes( const std::weak_ptr &channel, const QString &channelId, std::function callback, From b4026437798845ecfd52f4fad23e4d31f57ea235 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 16:18:55 -0400 Subject: [PATCH 08/19] dev: use getIApp for CompletionModel --- src/common/CompletionModel.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index 9b123aa4c6a..24ce495adb7 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -92,6 +92,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord) return; } + auto *app = getIApp(); // Twitch channel auto *tc = dynamic_cast(&this->channel_); @@ -130,7 +131,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord) } }; - if (auto account = getApp()->accounts->twitch.getCurrent()) + if (auto account = app->getAccounts()->twitch.getCurrent()) { // Twitch Emotes available globally for (const auto &emote : account->accessEmotes()->emotes) @@ -153,18 +154,18 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord) // 7TV Global for (const auto &emote : - *getApp()->twitch->getSeventvEmotes().globalEmotes()) + *app->getTwitch()->getSeventvEmotes().globalEmotes()) { addString(emote.first.string, TaggedString::Type::SeventvGlobalEmote); } // Bttv Global - for (const auto &emote : *getApp()->twitch->getBttvEmotes().emotes()) + for (const auto &emote : *app->getTwitch()->getBttvEmotes().emotes()) { addString(emote.first.string, TaggedString::Type::BTTVChannelEmote); } // Ffz Global - for (const auto &emote : *getApp()->twitch->getFfzEmotes().emotes()) + for (const auto &emote : *app->getTwitch()->getFfzEmotes().emotes()) { addString(emote.first.string, TaggedString::Type::FFZChannelEmote); } @@ -172,7 +173,8 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord) // Emojis if (prefix.startsWith(":")) { - const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes; + const auto &emojiShortCodes = + app->getEmotes()->getEmojis()->getShortCodes(); for (const auto &m : emojiShortCodes) { addString(QString(":%1:").arg(m), TaggedString::Type::Emoji); @@ -231,20 +233,20 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord) addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); } #ifdef CHATTERINO_HAVE_PLUGINS - for (const auto &command : getApp()->commands->pluginCommands()) + for (const auto &command : app->getCommands()->pluginCommands()) { addString(command, TaggedString::PluginCommand); } #endif // Custom Chatterino commands - for (const auto &command : getApp()->commands->items) + for (const auto &command : app->getCommands()->items) { addString(command.name, TaggedString::CustomCommand); } // Default Chatterino commands for (const auto &command : - getApp()->commands->getDefaultChatterinoCommandList()) + app->getCommands()->getDefaultChatterinoCommandList()) { addString(command, TaggedString::ChatterinoCommand); } From 93e3a9e534931b82c144d77f88e5f203ba4cbfb6 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 16:51:25 -0400 Subject: [PATCH 09/19] dev: add method to get all results from CompletionModel --- src/common/CompletionModel.cpp | 13 +++++++++++++ src/common/CompletionModel.hpp | 1 + 2 files changed, 14 insertions(+) diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index 24ce495adb7..a74a5491999 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -258,6 +258,19 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord) } } +std::vector CompletionModel::allItems() const +{ + std::unique_lock lock(this->itemsMutex_); + + std::vector results; + results.reserve(this->items_.size()); + for (const auto &item : this->items_) + { + results.push_back(item.string); + } + return results; +} + bool CompletionModel::compareStrings(const QString &a, const QString &b) { // try comparing insensitively, if they are the same then senstively diff --git a/src/common/CompletionModel.hpp b/src/common/CompletionModel.hpp index 5b46fb2de3b..9fff2d3288b 100644 --- a/src/common/CompletionModel.hpp +++ b/src/common/CompletionModel.hpp @@ -56,6 +56,7 @@ class CompletionModel : public QAbstractListModel int rowCount(const QModelIndex &parent) const override; void refresh(const QString &prefix, bool isFirstWord = false); + std::vector allItems() const; static bool compareStrings(const QString &a, const QString &b); From 756e83b78fcbaf21a227b6479b479a1e2c29db01 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 16:57:01 -0400 Subject: [PATCH 10/19] dev: add input completion tests --- tests/CMakeLists.txt | 1 + tests/src/InputCompletion.cpp | 336 ++++++++++++++++++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 tests/src/InputCompletion.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a3cb5ef0e59..f72c0492c5f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,6 +26,7 @@ set(test_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/Updates.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Filters.cpp ${CMAKE_CURRENT_LIST_DIR}/src/LinkParser.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/InputCompletion.cpp # Add your new file above this line! ) diff --git a/tests/src/InputCompletion.cpp b/tests/src/InputCompletion.cpp new file mode 100644 index 00000000000..73341aa35d3 --- /dev/null +++ b/tests/src/InputCompletion.cpp @@ -0,0 +1,336 @@ +#include "Application.hpp" +#include "BaseSettings.hpp" +#include "common/Aliases.hpp" +#include "common/CompletionModel.hpp" +#include "controllers/accounts/AccountController.hpp" +#include "messages/Emote.hpp" +#include "mocks/EmptyApplication.hpp" +#include "mocks/Helix.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" +#include "singletons/Emotes.hpp" +#include "singletons/Paths.hpp" +#include "singletons/Settings.hpp" +#include "widgets/splits/InputCompletionPopup.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace chatterino; +using ::testing::Exactly; + +class MockTwitchIrcServer : public ITwitchIrcServer +{ +public: + const BttvEmotes &getBttvEmotes() const override + { + return this->bttv; + } + + const FfzEmotes &getFfzEmotes() const override + { + return this->ffz; + } + + const SeventvEmotes &getSeventvEmotes() const override + { + return this->seventv; + } + + BttvEmotes bttv; + FfzEmotes ffz; + SeventvEmotes seventv; +}; + +class MockApplication : EmptyApplication +{ +public: + AccountController *getAccounts() override + { + return &this->accounts; + } + + ITwitchIrcServer *getTwitch() override + { + return &this->twitch; + } + + IEmotes *getEmotes() override + { + return &this->emotes; + } + + AccountController accounts; + MockTwitchIrcServer twitch; + Emotes emotes; +}; + +} // namespace + +namespace chatterino { + +class MockChannel : public Channel +{ +public: + MockChannel(const QString &name) + : Channel(name, Channel::Type::Twitch) + { + } +}; + +} // namespace chatterino + +EmotePtr namedEmote(const EmoteName &name) +{ + return std::shared_ptr(new Emote{ + .name{name}, + .images{}, + .tooltip{}, + .zeroWidth{}, + .id{}, + .author{}, + }); +} + +void addEmote(EmoteMap &map, const QString &name) +{ + EmoteName eName{.string{name}}; + map.insert(std::pair(eName, namedEmote(eName))); +} + +static QString DEFAULT_SETTINGS = R"!( +{ + "accounts": { + "uid117166826": { + "username": "testaccount_420", + "userID": "117166826", + "clientID": "abc", + "oauthToken": "def" + }, + "current": "testaccount_420" + } +})!"; + +class InputCompletionTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Write default settings to the mock settings json file + ASSERT_TRUE(QDir().mkpath("/tmp/c2-tests")); + + QFile settingsFile("/tmp/c2-tests/settings.json"); + ASSERT_TRUE(settingsFile.open(QIODevice::WriteOnly | QIODevice::Text)); + ASSERT_GT(settingsFile.write(DEFAULT_SETTINGS.toUtf8()), 0); + ASSERT_TRUE(settingsFile.flush()); + settingsFile.close(); + + // Initialize helix client + this->mockHelix = std::make_unique(); + initializeHelix(this->mockHelix.get()); + EXPECT_CALL(*this->mockHelix, loadBlocks).Times(Exactly(1)); + EXPECT_CALL(*this->mockHelix, update).Times(Exactly(1)); + + this->mockApplication = std::make_unique(); + this->settings = std::make_unique("/tmp/c2-tests"); + this->paths = std::make_unique(); + + this->mockApplication->accounts.initialize(*this->settings, + *this->paths); + this->mockApplication->emotes.initialize(*this->settings, *this->paths); + + this->channelPtr = std::make_shared("icelys"); + this->completionModel = + std::make_unique(*this->channelPtr); + + this->initializeEmotes(); + } + + void TearDown() override + { + ASSERT_TRUE(QDir("/tmp/c2-tests").removeRecursively()); + this->mockApplication.reset(); + this->settings.reset(); + this->paths.reset(); + this->mockHelix.reset(); + this->completionModel.reset(); + this->channelPtr.reset(); + } + + std::unique_ptr mockApplication; + std::unique_ptr settings; + std::unique_ptr paths; + std::unique_ptr mockHelix; + + ChannelPtr channelPtr; + std::unique_ptr completionModel; + +private: + void initializeEmotes() + { + auto bttvEmotes = std::make_shared(); + addEmote(*bttvEmotes, "FeelsGoodMan"); + addEmote(*bttvEmotes, "FeelsBadMan"); + addEmote(*bttvEmotes, "FeelsBirthdayMan"); + addEmote(*bttvEmotes, "Aware"); + addEmote(*bttvEmotes, "Clueless"); + addEmote(*bttvEmotes, "SaltyCorn"); + addEmote(*bttvEmotes, ":)"); + addEmote(*bttvEmotes, ":-)"); + addEmote(*bttvEmotes, "B-)"); + addEmote(*bttvEmotes, "Clap"); + this->mockApplication->twitch.bttv.setEmotes(std::move(bttvEmotes)); + + auto ffzEmotes = std::make_shared(); + addEmote(*ffzEmotes, "LilZ"); + addEmote(*ffzEmotes, "ManChicken"); + addEmote(*ffzEmotes, "CatBag"); + this->mockApplication->twitch.ffz.setEmotes(std::move(ffzEmotes)); + + auto seventvEmotes = std::make_shared(); + addEmote(*seventvEmotes, "Clap"); + addEmote(*seventvEmotes, "Clap2"); + this->mockApplication->twitch.seventv.setEmotes( + std::move(seventvEmotes)); + } + +protected: + auto queryEmoteCompletion(const QString &fullQuery) + { + // At the moment, buildCompletionEmoteList does not want the ':'. + QString normalizedQuery = fullQuery; + if (normalizedQuery.startsWith(':')) + { + normalizedQuery = normalizedQuery.mid(1); + } + + return chatterino::detail::buildCompletionEmoteList(normalizedQuery, + this->channelPtr); + } + + auto queryTabCompletion(const QString &fullQuery, bool isFirstWord) + { + this->completionModel->refresh(fullQuery, isFirstWord); + return this->completionModel->allItems(); + } +}; + +TEST_F(InputCompletionTest, EmoteNameFiltering) +{ + auto completion = queryEmoteCompletion(":feels"); + ASSERT_EQ(completion.size(), 3); + ASSERT_EQ(completion[0].displayName, "FeelsBirthdayMan"); + ASSERT_EQ(completion[1].displayName, "FeelsBadMan"); + ASSERT_EQ(completion[2].displayName, "FeelsGoodMan"); + + completion = queryEmoteCompletion(":)"); + ASSERT_EQ(completion.size(), 3); + ASSERT_EQ(completion[0].displayName, ":)"); // Exact match with : prefix + ASSERT_EQ(completion[1].displayName, ":-)"); + ASSERT_EQ(completion[2].displayName, "B-)"); + + completion = queryEmoteCompletion(":cat"); + ASSERT_TRUE(completion.size() >= 2); + // emoji exact match comes first + ASSERT_EQ(completion[0].displayName, "cat"); + // FFZ emote is prioritized over any other matching emojis + ASSERT_EQ(completion[1].displayName, "CatBag"); +} + +TEST_F(InputCompletionTest, EmoteExactNameMatching) +{ + auto completion = queryEmoteCompletion(":cat"); + ASSERT_TRUE(completion.size() >= 2); + // emoji exact match comes first + ASSERT_EQ(completion[0].displayName, "cat"); + // FFZ emote is prioritized over any other matching emojis + ASSERT_EQ(completion[1].displayName, "CatBag"); + + // not exactly "salt", SaltyCorn BTTV emote comes first + completion = queryEmoteCompletion(":sal"); + ASSERT_TRUE(completion.size() >= 3); + ASSERT_EQ(completion[0].displayName, "SaltyCorn"); + ASSERT_EQ(completion[1].displayName, "green_salad"); + ASSERT_EQ(completion[2].displayName, "salt"); + + // exactly "salt", emoji comes first + completion = queryEmoteCompletion(":salt"); + ASSERT_TRUE(completion.size() >= 2); + ASSERT_EQ(completion[0].displayName, "salt"); + ASSERT_EQ(completion[1].displayName, "SaltyCorn"); +} + +TEST_F(InputCompletionTest, EmoteProviderOrdering) +{ + auto completion = queryEmoteCompletion(":clap"); + // Current implementation leads to the exact first match being ignored when + // checking for exact matches. This is probably not intended behavior but + // this test is just verifying that the implementation stays the same. + // + // Initial ordering after filtering all available emotes: + // 1. Clap - BTTV + // 2. Clap - 7TV + // 3. Clap2 - 7TV + // 4. clapper - Emoji + // 5. clap - Emoji + // + // The 'exact match' starts looking at the second element and ends up swapping + // #2 with #1 despite #1 already being an exact match. + ASSERT_TRUE(completion.size() >= 5); + ASSERT_EQ(completion[0].displayName, "Clap"); + ASSERT_EQ(completion[0].providerName, "Global 7TV"); + ASSERT_EQ(completion[1].displayName, "Clap"); + ASSERT_EQ(completion[1].providerName, "Global BetterTTV"); + ASSERT_EQ(completion[2].displayName, "Clap2"); + ASSERT_EQ(completion[2].providerName, "Global 7TV"); + ASSERT_EQ(completion[3].displayName, "clapper"); + ASSERT_EQ(completion[3].providerName, "Emoji"); + ASSERT_EQ(completion[4].displayName, "clap"); + ASSERT_EQ(completion[4].providerName, "Emoji"); +} + +TEST_F(InputCompletionTest, TabCompletionEmote) +{ + auto completion = queryTabCompletion(":feels", false); + ASSERT_EQ(completion.size(), 0); // : prefix matters here + + // no : prefix defaults to emote completion + completion = queryTabCompletion("feels", false); + ASSERT_EQ(completion.size(), 3); + // note: different order from : menu + ASSERT_EQ(completion[0], "FeelsBadMan "); + ASSERT_EQ(completion[1], "FeelsBirthdayMan "); + ASSERT_EQ(completion[2], "FeelsGoodMan "); + + // no : prefix, emote completion. Duplicate Clap should be removed + completion = queryTabCompletion("cla", false); + ASSERT_EQ(completion.size(), 2); + ASSERT_EQ(completion[0], "Clap "); + ASSERT_EQ(completion[1], "Clap2 "); + + completion = queryTabCompletion("peepoHappy", false); + ASSERT_EQ(completion.size(), 0); // no peepoHappy emote + + completion = queryTabCompletion("Aware", false); + ASSERT_EQ(completion.size(), 1); + ASSERT_EQ(completion[0], "Aware "); // trailing space added +} + +TEST_F(InputCompletionTest, TabCompletionEmoji) +{ + auto completion = queryTabCompletion(":cla", false); + ASSERT_EQ(completion.size(), 8); + ASSERT_EQ(completion[0], ":clap: "); + ASSERT_EQ(completion[1], ":clap_tone1: "); + ASSERT_EQ(completion[2], ":clap_tone2: "); + ASSERT_EQ(completion[3], ":clap_tone3: "); + ASSERT_EQ(completion[4], ":clap_tone4: "); + ASSERT_EQ(completion[5], ":clap_tone5: "); + ASSERT_EQ(completion[6], ":clapper: "); + ASSERT_EQ(completion[7], ":classical_building: "); +} From 74f83a795a15ea4b23dfd172593aaa8b9ab9387d Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Fri, 19 May 2023 21:24:16 -0400 Subject: [PATCH 11/19] fix: initialize settings before using emojis --- benchmarks/src/Emojis.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/benchmarks/src/Emojis.cpp b/benchmarks/src/Emojis.cpp index 7eb5106e3e3..70043eb8375 100644 --- a/benchmarks/src/Emojis.cpp +++ b/benchmarks/src/Emojis.cpp @@ -1,5 +1,7 @@ #include "providers/emoji/Emojis.hpp" +#include "singletons/Settings.hpp" + #include #include #include @@ -8,6 +10,8 @@ using namespace chatterino; static void BM_ShortcodeParsing(benchmark::State &state) { + chatterino::Settings settings("/tmp/c2-mock"); + Emojis emojis; emojis.load(); From 7d8e049090025dd7dc94fec843477eb776af99df Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Sat, 20 May 2023 01:07:31 -0400 Subject: [PATCH 12/19] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d131fe763e..386996f3dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Bugfix: Fixed Ctrl+Backspace not working after Select All in chat search popup. (#4461) - Bugfix: Fixed crash when scrolling up really fast. (#4621) - Dev: Added the ability to control the `followRedirect` mode for requests. (#4594) +- Dev: Added test cases for emote and tab completion. (#4644) ## 2.4.3 From 33de8265a1127faa9221a32ae1dfdd7bdf841c7f Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 20 May 2023 13:15:24 +0200 Subject: [PATCH 13/19] Fix mock imports/usages --- tests/src/InputCompletion.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/InputCompletion.cpp b/tests/src/InputCompletion.cpp index 73341aa35d3..d7542587029 100644 --- a/tests/src/InputCompletion.cpp +++ b/tests/src/InputCompletion.cpp @@ -47,7 +47,7 @@ class MockTwitchIrcServer : public ITwitchIrcServer SeventvEmotes seventv; }; -class MockApplication : EmptyApplication +class MockApplication : mock::EmptyApplication { public: AccountController *getAccounts() override @@ -131,7 +131,7 @@ class InputCompletionTest : public ::testing::Test settingsFile.close(); // Initialize helix client - this->mockHelix = std::make_unique(); + this->mockHelix = std::make_unique(); initializeHelix(this->mockHelix.get()); EXPECT_CALL(*this->mockHelix, loadBlocks).Times(Exactly(1)); EXPECT_CALL(*this->mockHelix, update).Times(Exactly(1)); @@ -165,7 +165,7 @@ class InputCompletionTest : public ::testing::Test std::unique_ptr mockApplication; std::unique_ptr settings; std::unique_ptr paths; - std::unique_ptr mockHelix; + std::unique_ptr mockHelix; ChannelPtr channelPtr; std::unique_ptr completionModel; From 5582b3549e3be18b46c857353f89c65c3b449e43 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 20 May 2023 13:20:12 +0200 Subject: [PATCH 14/19] Fix benchmark 'empty path' --- benchmarks/src/Emojis.cpp | 4 ---- benchmarks/src/main.cpp | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/benchmarks/src/Emojis.cpp b/benchmarks/src/Emojis.cpp index 70043eb8375..7eb5106e3e3 100644 --- a/benchmarks/src/Emojis.cpp +++ b/benchmarks/src/Emojis.cpp @@ -1,7 +1,5 @@ #include "providers/emoji/Emojis.hpp" -#include "singletons/Settings.hpp" - #include #include #include @@ -10,8 +8,6 @@ using namespace chatterino; static void BM_ShortcodeParsing(benchmark::State &state) { - chatterino::Settings settings("/tmp/c2-mock"); - Emojis emojis; emojis.load(); diff --git a/benchmarks/src/main.cpp b/benchmarks/src/main.cpp index cc4491f22b5..5f4c967f888 100644 --- a/benchmarks/src/main.cpp +++ b/benchmarks/src/main.cpp @@ -13,7 +13,7 @@ int main(int argc, char **argv) ::benchmark::Initialize(&argc, argv); // Ensure settings are initialized before any tests are run - chatterino::Settings settings("/tmp/c2-empty-test"); + chatterino::Settings settings("/tmp/c2-empty-mock"); QtConcurrent::run([&app] { ::benchmark::RunSpecifiedBenchmarks(); From 60c234c25398343fa0eee09e1cb65451d4dc1edd Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 20 May 2023 13:32:02 +0200 Subject: [PATCH 15/19] Update twitch api README comment --- src/providers/twitch/api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/twitch/api/README.md b/src/providers/twitch/api/README.md index 00b00d289e4..19f51e16cd2 100644 --- a/src/providers/twitch/api/README.md +++ b/src/providers/twitch/api/README.md @@ -12,7 +12,7 @@ If you're adding support for a new endpoint, these are the things you should kno 1. Add a virtual function in the `IHelix` class. Naming should reflect the API name as best as possible. 1. Override the virtual function in the `Helix` class. -1. Mock the function in the `MockHelix` class in the `tests/src/mocks/Helix.cpp` file. +1. Mock the function in the `mock::Helix` class in the `mocks/include/mocks/Helix.hpp` file. 1. (Optional) Make a new error enum for the failure callback. For a simple example, see the `updateUserChatColor` function and its error enum `HelixUpdateUserChatColorError`. From 662ce358229704658d4a278628da88a759a9e7d8 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Sat, 20 May 2023 13:20:33 -0400 Subject: [PATCH 16/19] fix: rename setEmotes to setGlobalEmotes --- src/providers/seventv/SeventvEmotes.cpp | 7 ++++--- src/providers/seventv/SeventvEmotes.hpp | 2 +- tests/src/InputCompletion.cpp | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/providers/seventv/SeventvEmotes.cpp b/src/providers/seventv/SeventvEmotes.cpp index 864d8411955..321e43a6519 100644 --- a/src/providers/seventv/SeventvEmotes.cpp +++ b/src/providers/seventv/SeventvEmotes.cpp @@ -275,7 +275,7 @@ void SeventvEmotes::loadGlobalEmotes() { if (!Settings::instance().enableSevenTVGlobalEmotes) { - this->setEmotes(EMPTY_EMOTE_MAP); + this->setGlobalEmotes(EMPTY_EMOTE_MAP); return; } @@ -289,7 +289,8 @@ void SeventvEmotes::loadGlobalEmotes() auto emoteMap = parseEmotes(parsedEmotes, true); qCDebug(chatterinoSeventv) << "Loaded" << emoteMap.size() << "7TV Global Emotes"; - this->setEmotes(std::make_shared(std::move(emoteMap))); + this->setGlobalEmotes( + std::make_shared(std::move(emoteMap))); return Success; }) @@ -300,7 +301,7 @@ void SeventvEmotes::loadGlobalEmotes() .execute(); } -void SeventvEmotes::setEmotes(std::shared_ptr emotes) +void SeventvEmotes::setGlobalEmotes(std::shared_ptr emotes) { this->global_.set(std::move(emotes)); } diff --git a/src/providers/seventv/SeventvEmotes.hpp b/src/providers/seventv/SeventvEmotes.hpp index ea21ff7e1b7..7fea024bb19 100644 --- a/src/providers/seventv/SeventvEmotes.hpp +++ b/src/providers/seventv/SeventvEmotes.hpp @@ -75,7 +75,7 @@ class SeventvEmotes final std::shared_ptr globalEmotes() const; boost::optional globalEmote(const EmoteName &name) const; void loadGlobalEmotes(); - void setEmotes(std::shared_ptr emotes); + void setGlobalEmotes(std::shared_ptr emotes); static void loadChannelEmotes( const std::weak_ptr &channel, const QString &channelId, std::function callback, diff --git a/tests/src/InputCompletion.cpp b/tests/src/InputCompletion.cpp index d7542587029..e02edb145e8 100644 --- a/tests/src/InputCompletion.cpp +++ b/tests/src/InputCompletion.cpp @@ -195,7 +195,7 @@ class InputCompletionTest : public ::testing::Test auto seventvEmotes = std::make_shared(); addEmote(*seventvEmotes, "Clap"); addEmote(*seventvEmotes, "Clap2"); - this->mockApplication->twitch.seventv.setEmotes( + this->mockApplication->twitch.seventv.setGlobalEmotes( std::move(seventvEmotes)); } From cfdda786f7ab1560a340d600151a8c64846d6e0b Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Sat, 20 May 2023 13:22:57 -0400 Subject: [PATCH 17/19] fix: make CompletionModel::allItems private --- src/common/CompletionModel.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/CompletionModel.hpp b/src/common/CompletionModel.hpp index 9fff2d3288b..affbd3f10fd 100644 --- a/src/common/CompletionModel.hpp +++ b/src/common/CompletionModel.hpp @@ -6,6 +6,8 @@ #include #include +class InputCompletionTest; + namespace chatterino { class Channel; @@ -56,15 +58,18 @@ class CompletionModel : public QAbstractListModel int rowCount(const QModelIndex &parent) const override; void refresh(const QString &prefix, bool isFirstWord = false); - std::vector allItems() const; static bool compareStrings(const QString &a, const QString &b); private: + std::vector allItems() const; + mutable std::shared_mutex itemsMutex_; std::set items_; Channel &channel_; + + friend class ::InputCompletionTest; }; } // namespace chatterino From cfac94d917525bc2e9eb365e39b720b54b6fcc12 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Sat, 20 May 2023 13:23:24 -0400 Subject: [PATCH 18/19] fix: use shared_lock in CompletionModel::allItems --- src/common/CompletionModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index a74a5491999..61f1cc249c8 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -260,7 +260,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord) std::vector CompletionModel::allItems() const { - std::unique_lock lock(this->itemsMutex_); + std::shared_lock lock(this->itemsMutex_); std::vector results; results.reserve(this->items_.size()); From e40059dfa97f9a77ea13522e225f19d781ab81d0 Mon Sep 17 00:00:00 2001 From: Daniel Sage Date: Sat, 20 May 2023 13:38:06 -0400 Subject: [PATCH 19/19] fix: remove nested namespace --- src/widgets/splits/InputCompletionPopup.cpp | 130 ++++++++++---------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/src/widgets/splits/InputCompletionPopup.cpp b/src/widgets/splits/InputCompletionPopup.cpp index 886b6ecbc50..28eb829ae9f 100644 --- a/src/widgets/splits/InputCompletionPopup.cpp +++ b/src/widgets/splits/InputCompletionPopup.cpp @@ -48,93 +48,91 @@ void addEmojis(std::vector &out, const EmojiMap &map, } // namespace -namespace chatterino { - -namespace detail { +namespace chatterino::detail { - std::vector buildCompletionEmoteList(const QString &text, - ChannelPtr channel) +std::vector buildCompletionEmoteList(const QString &text, + ChannelPtr channel) +{ + std::vector emotes; + auto *app = getIApp(); + auto *tc = dynamic_cast(channel.get()); + // returns true also for special Twitch channels (/live, /mentions, /whispers, etc.) + if (channel->isTwitchChannel()) { - std::vector emotes; - auto *app = getIApp(); - auto *tc = dynamic_cast(channel.get()); - // returns true also for special Twitch channels (/live, /mentions, /whispers, etc.) - if (channel->isTwitchChannel()) + if (auto user = app->getAccounts()->twitch.getCurrent()) { - if (auto user = app->getAccounts()->twitch.getCurrent()) + // Twitch Emotes available globally + auto emoteData = user->accessEmotes(); + addEmotes(emotes, emoteData->emotes, text, "Twitch Emote"); + + // Twitch Emotes available locally + auto localEmoteData = user->accessLocalEmotes(); + if (tc && + localEmoteData->find(tc->roomId()) != localEmoteData->end()) { - // Twitch Emotes available globally - auto emoteData = user->accessEmotes(); - addEmotes(emotes, emoteData->emotes, text, "Twitch Emote"); - - // Twitch Emotes available locally - auto localEmoteData = user->accessLocalEmotes(); - if (tc && - localEmoteData->find(tc->roomId()) != localEmoteData->end()) + if (const auto *localEmotes = &localEmoteData->at(tc->roomId())) { - if (const auto *localEmotes = - &localEmoteData->at(tc->roomId())) - { - addEmotes(emotes, *localEmotes, text, - "Local Twitch Emotes"); - } - } - } - - if (tc) - { - // TODO extract "Channel {BetterTTV,7TV,FrankerFaceZ}" text into a #define. - if (auto bttv = tc->bttvEmotes()) - { - addEmotes(emotes, *bttv, text, "Channel BetterTTV"); - } - if (auto ffz = tc->ffzEmotes()) - { - addEmotes(emotes, *ffz, text, "Channel FrankerFaceZ"); - } - if (auto seventv = tc->seventvEmotes()) - { - addEmotes(emotes, *seventv, text, "Channel 7TV"); + addEmotes(emotes, *localEmotes, text, + "Local Twitch Emotes"); } } + } - if (auto bttvG = app->getTwitch()->getBttvEmotes().emotes()) + if (tc) + { + // TODO extract "Channel {BetterTTV,7TV,FrankerFaceZ}" text into a #define. + if (auto bttv = tc->bttvEmotes()) { - addEmotes(emotes, *bttvG, text, "Global BetterTTV"); + addEmotes(emotes, *bttv, text, "Channel BetterTTV"); } - if (auto ffzG = app->getTwitch()->getFfzEmotes().emotes()) + if (auto ffz = tc->ffzEmotes()) { - addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ"); + addEmotes(emotes, *ffz, text, "Channel FrankerFaceZ"); } - if (auto seventvG = - app->getTwitch()->getSeventvEmotes().globalEmotes()) + if (auto seventv = tc->seventvEmotes()) { - addEmotes(emotes, *seventvG, text, "Global 7TV"); + addEmotes(emotes, *seventv, text, "Channel 7TV"); } } - addEmojis(emotes, app->getEmotes()->getEmojis()->getEmojis(), text); - - // if there is an exact match, put that emote first - for (size_t i = 1; i < emotes.size(); i++) + if (auto bttvG = app->getTwitch()->getBttvEmotes().emotes()) { - auto emoteText = emotes.at(i).displayName; - - // test for match or match with colon at start for emotes like ":)" - if (emoteText.compare(text, Qt::CaseInsensitive) == 0 || - emoteText.compare(":" + text, Qt::CaseInsensitive) == 0) - { - auto emote = emotes[i]; - emotes.erase(emotes.begin() + int(i)); - emotes.insert(emotes.begin(), emote); - break; - } + addEmotes(emotes, *bttvG, text, "Global BetterTTV"); } + if (auto ffzG = app->getTwitch()->getFfzEmotes().emotes()) + { + addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ"); + } + if (auto seventvG = app->getTwitch()->getSeventvEmotes().globalEmotes()) + { + addEmotes(emotes, *seventvG, text, "Global 7TV"); + } + } - return emotes; + addEmojis(emotes, app->getEmotes()->getEmojis()->getEmojis(), text); + + // if there is an exact match, put that emote first + for (size_t i = 1; i < emotes.size(); i++) + { + auto emoteText = emotes.at(i).displayName; + + // test for match or match with colon at start for emotes like ":)" + if (emoteText.compare(text, Qt::CaseInsensitive) == 0 || + emoteText.compare(":" + text, Qt::CaseInsensitive) == 0) + { + auto emote = emotes[i]; + emotes.erase(emotes.begin() + int(i)); + emotes.insert(emotes.begin(), emote); + break; + } } -} // namespace detail + return emotes; +} + +} // namespace chatterino::detail + +namespace chatterino { InputCompletionPopup::InputCompletionPopup(QWidget *parent) : BasePopup({BasePopup::EnableCustomFrame, BasePopup::Frameless,