mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Add input completion test suite (#4644)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
e9f300b765
commit
51f2c4d1c0
24 changed files with 514 additions and 63 deletions
|
@ -5,6 +5,7 @@
|
||||||
- Minor: Added `/shoutout <username>` commands to shoutout specified user. (#4638)
|
- Minor: Added `/shoutout <username>` commands to shoutout specified user. (#4638)
|
||||||
- Dev: Added command to set Qt's logging filter/rules at runtime (`/c2-set-logging-rules`). (#4637)
|
- Dev: Added command to set Qt's logging filter/rules at runtime (`/c2-set-logging-rules`). (#4637)
|
||||||
- Dev: Added the ability to see & load custom themes from the Themes directory. No stable promises are made of this feature, changes might be made that breaks custom themes without notice. (#4570)
|
- Dev: Added the ability to see & load custom themes from the Themes directory. No stable promises are made of this feature, changes might be made that breaks custom themes without notice. (#4570)
|
||||||
|
- Dev: Added test cases for emote and tab completion. (#4644)
|
||||||
|
|
||||||
## 2.4.4
|
## 2.4.4
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ int main(int argc, char **argv)
|
||||||
::benchmark::Initialize(&argc, argv);
|
::benchmark::Initialize(&argc, argv);
|
||||||
|
|
||||||
// Ensure settings are initialized before any tests are run
|
// 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] {
|
QtConcurrent::run([&app] {
|
||||||
::benchmark::RunSpecifiedBenchmarks();
|
::benchmark::RunSpecifiedBenchmarks();
|
||||||
|
|
|
@ -57,7 +57,7 @@ public:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
TwitchIrcServer *getTwitch() override
|
ITwitchIrcServer *getTwitch() override
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,6 +245,11 @@ IUserDataController *Application::getUserData()
|
||||||
return this->userData;
|
return this->userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ITwitchIrcServer *Application::getTwitch()
|
||||||
|
{
|
||||||
|
return this->twitch;
|
||||||
|
}
|
||||||
|
|
||||||
void Application::save()
|
void Application::save()
|
||||||
{
|
{
|
||||||
for (auto &singleton : this->singletons_)
|
for (auto &singleton : this->singletons_)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchIrcServer;
|
class TwitchIrcServer;
|
||||||
|
class ITwitchIrcServer;
|
||||||
class PubSub;
|
class PubSub;
|
||||||
|
|
||||||
class CommandController;
|
class CommandController;
|
||||||
|
@ -55,7 +56,7 @@ public:
|
||||||
virtual CommandController *getCommands() = 0;
|
virtual CommandController *getCommands() = 0;
|
||||||
virtual HighlightController *getHighlights() = 0;
|
virtual HighlightController *getHighlights() = 0;
|
||||||
virtual NotificationController *getNotifications() = 0;
|
virtual NotificationController *getNotifications() = 0;
|
||||||
virtual TwitchIrcServer *getTwitch() = 0;
|
virtual ITwitchIrcServer *getTwitch() = 0;
|
||||||
virtual ChatterinoBadges *getChatterinoBadges() = 0;
|
virtual ChatterinoBadges *getChatterinoBadges() = 0;
|
||||||
virtual FfzBadges *getFfzBadges() = 0;
|
virtual FfzBadges *getFfzBadges() = 0;
|
||||||
virtual IUserDataController *getUserData() = 0;
|
virtual IUserDataController *getUserData() = 0;
|
||||||
|
@ -141,10 +142,7 @@ public:
|
||||||
{
|
{
|
||||||
return this->highlights;
|
return this->highlights;
|
||||||
}
|
}
|
||||||
TwitchIrcServer *getTwitch() override
|
ITwitchIrcServer *getTwitch() override;
|
||||||
{
|
|
||||||
return this->twitch;
|
|
||||||
}
|
|
||||||
ChatterinoBadges *getChatterinoBadges() override
|
ChatterinoBadges *getChatterinoBadges() override
|
||||||
{
|
{
|
||||||
return this->chatterinoBadges;
|
return this->chatterinoBadges;
|
||||||
|
|
|
@ -92,6 +92,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto *app = getIApp();
|
||||||
// Twitch channel
|
// Twitch channel
|
||||||
auto *tc = dynamic_cast<TwitchChannel *>(&this->channel_);
|
auto *tc = dynamic_cast<TwitchChannel *>(&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
|
// Twitch Emotes available globally
|
||||||
for (const auto &emote : account->accessEmotes()->emotes)
|
for (const auto &emote : account->accessEmotes()->emotes)
|
||||||
|
@ -153,18 +154,18 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
|
|
||||||
// 7TV Global
|
// 7TV Global
|
||||||
for (const auto &emote :
|
for (const auto &emote :
|
||||||
*getApp()->twitch->getSeventvEmotes().globalEmotes())
|
*app->getTwitch()->getSeventvEmotes().globalEmotes())
|
||||||
{
|
{
|
||||||
addString(emote.first.string, TaggedString::Type::SeventvGlobalEmote);
|
addString(emote.first.string, TaggedString::Type::SeventvGlobalEmote);
|
||||||
}
|
}
|
||||||
// Bttv Global
|
// 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);
|
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ffz Global
|
// 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);
|
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
||||||
}
|
}
|
||||||
|
@ -172,7 +173,8 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
// Emojis
|
// Emojis
|
||||||
if (prefix.startsWith(":"))
|
if (prefix.startsWith(":"))
|
||||||
{
|
{
|
||||||
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
|
const auto &emojiShortCodes =
|
||||||
|
app->getEmotes()->getEmojis()->getShortCodes();
|
||||||
for (const auto &m : emojiShortCodes)
|
for (const auto &m : emojiShortCodes)
|
||||||
{
|
{
|
||||||
addString(QString(":%1:").arg(m), TaggedString::Type::Emoji);
|
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);
|
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||||
}
|
}
|
||||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
for (const auto &command : getApp()->commands->pluginCommands())
|
for (const auto &command : app->getCommands()->pluginCommands())
|
||||||
{
|
{
|
||||||
addString(command, TaggedString::PluginCommand);
|
addString(command, TaggedString::PluginCommand);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Custom Chatterino commands
|
// Custom Chatterino commands
|
||||||
for (const auto &command : getApp()->commands->items)
|
for (const auto &command : app->getCommands()->items)
|
||||||
{
|
{
|
||||||
addString(command.name, TaggedString::CustomCommand);
|
addString(command.name, TaggedString::CustomCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default Chatterino commands
|
// Default Chatterino commands
|
||||||
for (const auto &command :
|
for (const auto &command :
|
||||||
getApp()->commands->getDefaultChatterinoCommandList())
|
app->getCommands()->getDefaultChatterinoCommandList())
|
||||||
{
|
{
|
||||||
addString(command, TaggedString::ChatterinoCommand);
|
addString(command, TaggedString::ChatterinoCommand);
|
||||||
}
|
}
|
||||||
|
@ -256,6 +258,19 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<QString> CompletionModel::allItems() const
|
||||||
|
{
|
||||||
|
std::shared_lock lock(this->itemsMutex_);
|
||||||
|
|
||||||
|
std::vector<QString> 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)
|
bool CompletionModel::compareStrings(const QString &a, const QString &b)
|
||||||
{
|
{
|
||||||
// try comparing insensitively, if they are the same then senstively
|
// try comparing insensitively, if they are the same then senstively
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
class InputCompletionTest;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Channel;
|
class Channel;
|
||||||
|
@ -60,10 +62,14 @@ public:
|
||||||
static bool compareStrings(const QString &a, const QString &b);
|
static bool compareStrings(const QString &a, const QString &b);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::vector<QString> allItems() const;
|
||||||
|
|
||||||
mutable std::shared_mutex itemsMutex_;
|
mutable std::shared_mutex itemsMutex_;
|
||||||
std::set<TaggedString> items_;
|
std::set<TaggedString> items_;
|
||||||
|
|
||||||
Channel &channel_;
|
Channel &channel_;
|
||||||
|
|
||||||
|
friend class ::InputCompletionTest;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -193,7 +193,7 @@ void BttvEmotes::loadEmotes()
|
||||||
{
|
{
|
||||||
if (!Settings::instance().enableBTTVGlobalEmotes)
|
if (!Settings::instance().enableBTTVGlobalEmotes)
|
||||||
{
|
{
|
||||||
this->global_.set(EMPTY_EMOTE_MAP);
|
this->setEmotes(EMPTY_EMOTE_MAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,13 +203,18 @@ void BttvEmotes::loadEmotes()
|
||||||
auto emotes = this->global_.get();
|
auto emotes = this->global_.get();
|
||||||
auto pair = parseGlobalEmotes(result.parseJsonArray(), *emotes);
|
auto pair = parseGlobalEmotes(result.parseJsonArray(), *emotes);
|
||||||
if (pair.first)
|
if (pair.first)
|
||||||
this->global_.set(
|
this->setEmotes(
|
||||||
std::make_shared<EmoteMap>(std::move(pair.second)));
|
std::make_shared<EmoteMap>(std::move(pair.second)));
|
||||||
return pair.first;
|
return pair.first;
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BttvEmotes::setEmotes(std::shared_ptr<const EmoteMap> emotes)
|
||||||
|
{
|
||||||
|
this->global_.set(std::move(emotes));
|
||||||
|
}
|
||||||
|
|
||||||
void BttvEmotes::loadChannel(std::weak_ptr<Channel> channel,
|
void BttvEmotes::loadChannel(std::weak_ptr<Channel> channel,
|
||||||
const QString &channelId,
|
const QString &channelId,
|
||||||
const QString &channelDisplayName,
|
const QString &channelDisplayName,
|
||||||
|
|
|
@ -29,6 +29,7 @@ public:
|
||||||
std::shared_ptr<const EmoteMap> emotes() const;
|
std::shared_ptr<const EmoteMap> emotes() const;
|
||||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||||
void loadEmotes();
|
void loadEmotes();
|
||||||
|
void setEmotes(std::shared_ptr<const EmoteMap> emotes);
|
||||||
static void loadChannel(std::weak_ptr<Channel> channel,
|
static void loadChannel(std::weak_ptr<Channel> channel,
|
||||||
const QString &channelId,
|
const QString &channelId,
|
||||||
const QString &channelDisplayName,
|
const QString &channelDisplayName,
|
||||||
|
|
|
@ -265,7 +265,7 @@ void Emojis::loadEmojiSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||||
const QString &text)
|
const QString &text) const
|
||||||
{
|
{
|
||||||
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
||||||
int lastParsedEmojiEndIndex = 0;
|
int lastParsedEmojiEndIndex = 0;
|
||||||
|
@ -359,7 +359,7 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Emojis::replaceShortCodes(const QString &text)
|
QString Emojis::replaceShortCodes(const QString &text) const
|
||||||
{
|
{
|
||||||
QString ret(text);
|
QString ret(text);
|
||||||
auto it = this->findShortCodesRegex_.globalMatch(text);
|
auto it = this->findShortCodesRegex_.globalMatch(text);
|
||||||
|
@ -393,4 +393,14 @@ QString Emojis::replaceShortCodes(const QString &text)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EmojiMap &Emojis::getEmojis() const
|
||||||
|
{
|
||||||
|
return this->emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<QString> &Emojis::getShortCodes() const
|
||||||
|
{
|
||||||
|
return this->shortCodes;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -37,16 +37,32 @@ struct EmojiData {
|
||||||
|
|
||||||
using EmojiMap = ConcurrentMap<QString, std::shared_ptr<EmojiData>>;
|
using EmojiMap = ConcurrentMap<QString, std::shared_ptr<EmojiData>>;
|
||||||
|
|
||||||
class Emojis
|
class IEmojis
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IEmojis() = default;
|
||||||
|
|
||||||
|
virtual std::vector<boost::variant<EmotePtr, QString>> parse(
|
||||||
|
const QString &text) const = 0;
|
||||||
|
virtual const EmojiMap &getEmojis() const = 0;
|
||||||
|
virtual const std::vector<QString> &getShortCodes() const = 0;
|
||||||
|
virtual QString replaceShortCodes(const QString &text) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Emojis : public IEmojis
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void initialize();
|
void initialize();
|
||||||
void load();
|
void load();
|
||||||
std::vector<boost::variant<EmotePtr, QString>> parse(const QString &text);
|
std::vector<boost::variant<EmotePtr, QString>> parse(
|
||||||
|
const QString &text) const override;
|
||||||
|
|
||||||
EmojiMap emojis;
|
EmojiMap emojis;
|
||||||
std::vector<QString> shortCodes;
|
std::vector<QString> shortCodes;
|
||||||
QString replaceShortCodes(const QString &text);
|
QString replaceShortCodes(const QString &text) const override;
|
||||||
|
|
||||||
|
const EmojiMap &getEmojis() const override;
|
||||||
|
const std::vector<QString> &getShortCodes() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadEmojis();
|
void loadEmojis();
|
||||||
|
|
|
@ -188,7 +188,7 @@ void FfzEmotes::loadEmotes()
|
||||||
{
|
{
|
||||||
if (!Settings::instance().enableFFZGlobalEmotes)
|
if (!Settings::instance().enableFFZGlobalEmotes)
|
||||||
{
|
{
|
||||||
this->global_.set(EMPTY_EMOTE_MAP);
|
this->setEmotes(EMPTY_EMOTE_MAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,13 +199,18 @@ void FfzEmotes::loadEmotes()
|
||||||
.timeout(30000)
|
.timeout(30000)
|
||||||
.onSuccess([this](auto result) -> Outcome {
|
.onSuccess([this](auto result) -> Outcome {
|
||||||
auto parsedSet = parseGlobalEmotes(result.parseJson());
|
auto parsedSet = parseGlobalEmotes(result.parseJson());
|
||||||
this->global_.set(std::make_shared<EmoteMap>(std::move(parsedSet)));
|
this->setEmotes(std::make_shared<EmoteMap>(std::move(parsedSet)));
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FfzEmotes::setEmotes(std::shared_ptr<const EmoteMap> emotes)
|
||||||
|
{
|
||||||
|
this->global_.set(std::move(emotes));
|
||||||
|
}
|
||||||
|
|
||||||
void FfzEmotes::loadChannel(
|
void FfzEmotes::loadChannel(
|
||||||
std::weak_ptr<Channel> channel, const QString &channelID,
|
std::weak_ptr<Channel> channel, const QString &channelID,
|
||||||
std::function<void(EmoteMap &&)> emoteCallback,
|
std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
|
|
|
@ -22,6 +22,7 @@ public:
|
||||||
std::shared_ptr<const EmoteMap> emotes() const;
|
std::shared_ptr<const EmoteMap> emotes() const;
|
||||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||||
void loadEmotes();
|
void loadEmotes();
|
||||||
|
void setEmotes(std::shared_ptr<const EmoteMap> emotes);
|
||||||
static void loadChannel(
|
static void loadChannel(
|
||||||
std::weak_ptr<Channel> channel, const QString &channelId,
|
std::weak_ptr<Channel> channel, const QString &channelId,
|
||||||
std::function<void(EmoteMap &&)> emoteCallback,
|
std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
|
|
|
@ -275,7 +275,7 @@ void SeventvEmotes::loadGlobalEmotes()
|
||||||
{
|
{
|
||||||
if (!Settings::instance().enableSevenTVGlobalEmotes)
|
if (!Settings::instance().enableSevenTVGlobalEmotes)
|
||||||
{
|
{
|
||||||
this->global_.set(EMPTY_EMOTE_MAP);
|
this->setGlobalEmotes(EMPTY_EMOTE_MAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +289,8 @@ void SeventvEmotes::loadGlobalEmotes()
|
||||||
auto emoteMap = parseEmotes(parsedEmotes, true);
|
auto emoteMap = parseEmotes(parsedEmotes, true);
|
||||||
qCDebug(chatterinoSeventv)
|
qCDebug(chatterinoSeventv)
|
||||||
<< "Loaded" << emoteMap.size() << "7TV Global Emotes";
|
<< "Loaded" << emoteMap.size() << "7TV Global Emotes";
|
||||||
this->global_.set(std::make_shared<EmoteMap>(std::move(emoteMap)));
|
this->setGlobalEmotes(
|
||||||
|
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
})
|
})
|
||||||
|
@ -300,6 +301,11 @@ void SeventvEmotes::loadGlobalEmotes()
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SeventvEmotes::setGlobalEmotes(std::shared_ptr<const EmoteMap> emotes)
|
||||||
|
{
|
||||||
|
this->global_.set(std::move(emotes));
|
||||||
|
}
|
||||||
|
|
||||||
void SeventvEmotes::loadChannelEmotes(
|
void SeventvEmotes::loadChannelEmotes(
|
||||||
const std::weak_ptr<Channel> &channel, const QString &channelId,
|
const std::weak_ptr<Channel> &channel, const QString &channelId,
|
||||||
std::function<void(EmoteMap &&, ChannelInfo)> callback, bool manualRefresh)
|
std::function<void(EmoteMap &&, ChannelInfo)> callback, bool manualRefresh)
|
||||||
|
|
|
@ -75,6 +75,7 @@ public:
|
||||||
std::shared_ptr<const EmoteMap> globalEmotes() const;
|
std::shared_ptr<const EmoteMap> globalEmotes() const;
|
||||||
boost::optional<EmotePtr> globalEmote(const EmoteName &name) const;
|
boost::optional<EmotePtr> globalEmote(const EmoteName &name) const;
|
||||||
void loadGlobalEmotes();
|
void loadGlobalEmotes();
|
||||||
|
void setGlobalEmotes(std::shared_ptr<const EmoteMap> emotes);
|
||||||
static void loadChannelEmotes(
|
static void loadChannelEmotes(
|
||||||
const std::weak_ptr<Channel> &channel, const QString &channelId,
|
const std::weak_ptr<Channel> &channel, const QString &channelId,
|
||||||
std::function<void(EmoteMap &&, ChannelInfo)> callback,
|
std::function<void(EmoteMap &&, ChannelInfo)> callback,
|
||||||
|
|
|
@ -23,7 +23,21 @@ class TwitchChannel;
|
||||||
class BttvLiveUpdates;
|
class BttvLiveUpdates;
|
||||||
class SeventvEventAPI;
|
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:
|
public:
|
||||||
TwitchIrcServer();
|
TwitchIrcServer();
|
||||||
|
@ -70,9 +84,9 @@ public:
|
||||||
std::unique_ptr<BttvLiveUpdates> bttvLiveUpdates;
|
std::unique_ptr<BttvLiveUpdates> bttvLiveUpdates;
|
||||||
std::unique_ptr<SeventvEventAPI> seventvEventAPI;
|
std::unique_ptr<SeventvEventAPI> seventvEventAPI;
|
||||||
|
|
||||||
const BttvEmotes &getBttvEmotes() const;
|
const BttvEmotes &getBttvEmotes() const override;
|
||||||
const FfzEmotes &getFfzEmotes() const;
|
const FfzEmotes &getFfzEmotes() const override;
|
||||||
const SeventvEmotes &getSeventvEmotes() const;
|
const SeventvEmotes &getSeventvEmotes() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void initializeConnection(IrcConnection *connection,
|
virtual void initializeConnection(IrcConnection *connection,
|
||||||
|
|
|
@ -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. 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. 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 `mock::Helix` class in the `mocks/include/mocks/Helix.hpp` file.
|
||||||
1. (Optional) Make a new error enum for the failure callback.
|
1. (Optional) Make a new error enum for the failure callback.
|
||||||
|
|
||||||
For a simple example, see the `updateUserChatColor` function and its error enum `HelixUpdateUserChatColorError`.
|
For a simple example, see the `updateUserChatColor` function and its error enum `HelixUpdateUserChatColorError`.
|
||||||
|
|
|
@ -16,6 +16,7 @@ public:
|
||||||
virtual ~IEmotes() = default;
|
virtual ~IEmotes() = default;
|
||||||
|
|
||||||
virtual ITwitchEmotes *getTwitchEmotes() = 0;
|
virtual ITwitchEmotes *getTwitchEmotes() = 0;
|
||||||
|
virtual IEmojis *getEmojis() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Emotes final : public IEmotes, public Singleton
|
class Emotes final : public IEmotes, public Singleton
|
||||||
|
@ -32,6 +33,11 @@ public:
|
||||||
return &this->twitch;
|
return &this->twitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEmojis *getEmojis() final
|
||||||
|
{
|
||||||
|
return &this->emojis;
|
||||||
|
}
|
||||||
|
|
||||||
TwitchEmotes twitch;
|
TwitchEmotes twitch;
|
||||||
Emojis emojis;
|
Emojis emojis;
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,7 @@
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
|
using namespace chatterino::detail;
|
||||||
struct CompletionEmote {
|
|
||||||
EmotePtr emote;
|
|
||||||
QString displayName;
|
|
||||||
QString providerName;
|
|
||||||
};
|
|
||||||
|
|
||||||
void addEmotes(std::vector<CompletionEmote> &out, const EmoteMap &map,
|
void addEmotes(std::vector<CompletionEmote> &out, const EmoteMap &map,
|
||||||
const QString &text, const QString &providerName)
|
const QString &text, const QString &providerName)
|
||||||
|
@ -53,33 +48,18 @@ void addEmojis(std::vector<CompletionEmote> &out, const EmojiMap &map,
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino::detail {
|
||||||
|
|
||||||
InputCompletionPopup::InputCompletionPopup(QWidget *parent)
|
std::vector<CompletionEmote> buildCompletionEmoteList(const QString &text,
|
||||||
: BasePopup({BasePopup::EnableCustomFrame, BasePopup::Frameless,
|
ChannelPtr channel)
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel)
|
|
||||||
{
|
{
|
||||||
std::vector<CompletionEmote> emotes;
|
std::vector<CompletionEmote> emotes;
|
||||||
|
auto *app = getIApp();
|
||||||
auto *tc = dynamic_cast<TwitchChannel *>(channel.get());
|
auto *tc = dynamic_cast<TwitchChannel *>(channel.get());
|
||||||
// returns true also for special Twitch channels (/live, /mentions, /whispers, etc.)
|
// returns true also for special Twitch channels (/live, /mentions, /whispers, etc.)
|
||||||
if (channel->isTwitchChannel())
|
if (channel->isTwitchChannel())
|
||||||
{
|
{
|
||||||
if (auto user = getApp()->accounts->twitch.getCurrent())
|
if (auto user = app->getAccounts()->twitch.getCurrent())
|
||||||
{
|
{
|
||||||
// Twitch Emotes available globally
|
// Twitch Emotes available globally
|
||||||
auto emoteData = user->accessEmotes();
|
auto emoteData = user->accessEmotes();
|
||||||
|
@ -115,21 +95,21 @@ void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto bttvG = getApp()->twitch->getBttvEmotes().emotes())
|
if (auto bttvG = app->getTwitch()->getBttvEmotes().emotes())
|
||||||
{
|
{
|
||||||
addEmotes(emotes, *bttvG, text, "Global BetterTTV");
|
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");
|
addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ");
|
||||||
}
|
}
|
||||||
if (auto seventvG = getApp()->twitch->getSeventvEmotes().globalEmotes())
|
if (auto seventvG = app->getTwitch()->getSeventvEmotes().globalEmotes())
|
||||||
{
|
{
|
||||||
addEmotes(emotes, *seventvG, text, "Global 7TV");
|
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
|
// if there is an exact match, put that emote first
|
||||||
for (size_t i = 1; i < emotes.size(); i++)
|
for (size_t i = 1; i < emotes.size(); i++)
|
||||||
|
@ -147,6 +127,34 @@ void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return emotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino::detail
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel)
|
||||||
|
{
|
||||||
|
auto emotes = detail::buildCompletionEmoteList(text, std::move(channel));
|
||||||
|
|
||||||
this->model_.clear();
|
this->model_.clear();
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
|
@ -5,12 +5,29 @@
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Channel;
|
class Channel;
|
||||||
using ChannelPtr = std::shared_ptr<Channel>;
|
using ChannelPtr = std::shared_ptr<Channel>;
|
||||||
|
|
||||||
|
struct Emote;
|
||||||
|
using EmotePtr = std::shared_ptr<const Emote>;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
struct CompletionEmote {
|
||||||
|
EmotePtr emote;
|
||||||
|
QString displayName;
|
||||||
|
QString providerName;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<CompletionEmote> buildCompletionEmoteList(const QString &text,
|
||||||
|
ChannelPtr channel);
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
class GenericListView;
|
class GenericListView;
|
||||||
|
|
||||||
class InputCompletionPopup : public BasePopup
|
class InputCompletionPopup : public BasePopup
|
||||||
|
|
|
@ -26,6 +26,7 @@ set(test_SOURCES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/Updates.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/Updates.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/Filters.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/Filters.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/LinkParser.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/LinkParser.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/InputCompletion.cpp
|
||||||
# Add your new file above this line!
|
# Add your new file above this line!
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
|
||||||
#include "BaseSettings.hpp"
|
#include "BaseSettings.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
|
|
336
tests/src/InputCompletion.cpp
Normal file
336
tests/src/InputCompletion.cpp
Normal file
|
@ -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 <boost/optional/optional_io.hpp>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QModelIndex>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
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 : mock::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<Emote>(new Emote{
|
||||||
|
.name{name},
|
||||||
|
.images{},
|
||||||
|
.tooltip{},
|
||||||
|
.zeroWidth{},
|
||||||
|
.id{},
|
||||||
|
.author{},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void addEmote(EmoteMap &map, const QString &name)
|
||||||
|
{
|
||||||
|
EmoteName eName{.string{name}};
|
||||||
|
map.insert(std::pair<EmoteName, EmotePtr>(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<mock::Helix>();
|
||||||
|
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<MockApplication>();
|
||||||
|
this->settings = std::make_unique<Settings>("/tmp/c2-tests");
|
||||||
|
this->paths = std::make_unique<Paths>();
|
||||||
|
|
||||||
|
this->mockApplication->accounts.initialize(*this->settings,
|
||||||
|
*this->paths);
|
||||||
|
this->mockApplication->emotes.initialize(*this->settings, *this->paths);
|
||||||
|
|
||||||
|
this->channelPtr = std::make_shared<MockChannel>("icelys");
|
||||||
|
this->completionModel =
|
||||||
|
std::make_unique<CompletionModel>(*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> mockApplication;
|
||||||
|
std::unique_ptr<Settings> settings;
|
||||||
|
std::unique_ptr<Paths> paths;
|
||||||
|
std::unique_ptr<mock::Helix> mockHelix;
|
||||||
|
|
||||||
|
ChannelPtr channelPtr;
|
||||||
|
std::unique_ptr<CompletionModel> completionModel;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initializeEmotes()
|
||||||
|
{
|
||||||
|
auto bttvEmotes = std::make_shared<EmoteMap>();
|
||||||
|
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<EmoteMap>();
|
||||||
|
addEmote(*ffzEmotes, "LilZ");
|
||||||
|
addEmote(*ffzEmotes, "ManChicken");
|
||||||
|
addEmote(*ffzEmotes, "CatBag");
|
||||||
|
this->mockApplication->twitch.ffz.setEmotes(std::move(ffzEmotes));
|
||||||
|
|
||||||
|
auto seventvEmotes = std::make_shared<EmoteMap>();
|
||||||
|
addEmote(*seventvEmotes, "Clap");
|
||||||
|
addEmote(*seventvEmotes, "Clap2");
|
||||||
|
this->mockApplication->twitch.seventv.setGlobalEmotes(
|
||||||
|
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: ");
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "mocks/EmptyApplication.hpp"
|
#include "mocks/EmptyApplication.hpp"
|
||||||
|
@ -27,6 +26,7 @@ public:
|
||||||
{
|
{
|
||||||
return &this->emotes;
|
return &this->emotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
IUserDataController *getUserData() override
|
IUserDataController *getUserData() override
|
||||||
{
|
{
|
||||||
return &this->userData;
|
return &this->userData;
|
||||||
|
|
Loading…
Reference in a new issue