test: add more input completion tests (#5604)

This commit is contained in:
nerix 2024-09-29 12:20:06 +02:00 committed by GitHub
parent ef4be6cdac
commit ef3c51a4e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 269 additions and 31 deletions

View file

@ -94,5 +94,5 @@ jobs:
cd ../pubsub-server-test
./server 127.0.0.1:9050 &
cd ../build-test
ctest --repeat until-pass:4 --output-on-failure --exclude-regex ClassicEmoteNameFiltering
ctest --repeat until-pass:4 --output-on-failure
working-directory: build-test

View file

@ -150,7 +150,7 @@ jobs:
cd ..\pubsub-server-test
.\server.exe 127.0.0.1:9050 &
cd ..\build-test
ctest --repeat until-pass:4 --output-on-failure --exclude-regex ClassicEmoteNameFiltering
ctest --repeat until-pass:4 --output-on-failure
working-directory: build-test
- name: Clean Conan cache

View file

@ -88,7 +88,7 @@ jobs:
cd ../pubsub-server-test
./server 127.0.0.1:9050 &
cd ../build-test
ctest --repeat until-pass:4 --output-on-failure --exclude-regex ClassicEmoteNameFiltering
ctest --repeat until-pass:4 --output-on-failure
working-directory: build-test
- name: Upload coverage reports to Codecov

View file

@ -90,6 +90,7 @@
- Dev: The timer for `StreamerMode` is now destroyed on the correct thread. (#5571)
- Dev: Cleanup some parts of the `magic_enum` adaptation for Qt. (#5587)
- Dev: Refactored `static`s in headers to only be present once in the final app. (#5588)
- Dev: Added more tests for input completion. (#5604)
- Dev: Refactored legacy Unicode zero-width-joiner replacement. (#5594)
- Dev: The JSON output when copying a message (<kbd>SHIFT</kbd> + right-click) is now more extensive. (#5600)
- Dev: Twitch messages are now sent using Twitch's Helix API instead of IRC by default. (#5607)

View file

@ -3,6 +3,7 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/completion/strategies/ClassicEmoteStrategy.hpp"
#include "controllers/completion/strategies/ClassicUserStrategy.hpp"
#include "controllers/completion/strategies/SmartEmoteStrategy.hpp"
#include "controllers/completion/strategies/Strategy.hpp"
#include "messages/Emote.hpp"
#include "mocks/BaseApplication.hpp"
@ -84,7 +85,30 @@ public:
SeventvEmotes seventvEmotes;
};
} // namespace
void containsRoughly(std::span<EmoteItem> span, const std::set<QString> &values)
{
for (const auto &v : values)
{
bool found = false;
for (const auto &actualValue : span)
{
if (actualValue.displayName == v)
{
found = true;
break;
}
}
ASSERT_TRUE(found) << v << " was not found in the span";
}
}
[[nodiscard]] bool allEmoji(std::span<EmoteItem> span)
{
return std::ranges::all_of(span, [](const auto &it) {
return it.isEmoji && it.providerName == u"Emoji";
});
}
EmotePtr namedEmote(const EmoteName &name)
{
@ -104,7 +128,7 @@ void addEmote(EmoteMap &map, const QString &name)
map.insert(std::pair<EmoteName, EmotePtr>(eName, namedEmote(eName)));
}
static QString DEFAULT_SETTINGS = R"!(
const QString DEFAULT_SETTINGS = R"!(
{
"accounts": {
"uid117166826": {
@ -117,6 +141,8 @@ static QString DEFAULT_SETTINGS = R"!(
}
})!";
} // namespace
class InputCompletionTest : public ::testing::Test
{
protected:
@ -164,6 +190,7 @@ private:
addEmote(*bttvEmotes, ":-)");
addEmote(*bttvEmotes, "B-)");
addEmote(*bttvEmotes, "Clap");
addEmote(*bttvEmotes, ":tf:");
this->mockApplication->bttvEmotes.setEmotes(std::move(bttvEmotes));
auto ffzEmotes = std::make_shared<EmoteMap>();
@ -175,51 +202,57 @@ private:
auto seventvEmotes = std::make_shared<EmoteMap>();
addEmote(*seventvEmotes, "Clap");
addEmote(*seventvEmotes, "Clap2");
addEmote(*seventvEmotes, "pajaW");
addEmote(*seventvEmotes, "PAJAW");
this->mockApplication->seventvEmotes.setGlobalEmotes(
std::move(seventvEmotes));
}
protected:
auto queryClassicEmoteCompletion(const QString &fullQuery)
template <typename T>
auto queryEmoteCompletion(const QString &fullQuery)
{
EmoteSource source(this->channelPtr.get(),
std::make_unique<ClassicEmoteStrategy>());
EmoteSource source(this->channelPtr.get(), std::make_unique<T>());
source.update(fullQuery);
std::vector<EmoteItem> out(source.output());
return out;
}
auto queryClassicTabCompletion(const QString &fullQuery, bool isFirstWord)
template <typename T>
auto queryTabCompletion(const QString &fullQuery, bool isFirstWord)
{
EmoteSource source(this->channelPtr.get(),
std::make_unique<ClassicTabEmoteStrategy>());
EmoteSource source(this->channelPtr.get(), std::make_unique<T>());
source.update(fullQuery);
QStringList m;
source.addToStringList(m, 0, isFirstWord);
return m;
}
auto queryClassicEmoteCompletion(const QString &fullQuery)
{
return queryEmoteCompletion<ClassicEmoteStrategy>(fullQuery);
}
auto queryClassicTabCompletion(const QString &fullQuery, bool isFirstWord)
{
return queryTabCompletion<ClassicTabEmoteStrategy>(fullQuery,
isFirstWord);
}
auto querySmartEmoteCompletion(const QString &fullQuery)
{
return queryEmoteCompletion<SmartEmoteStrategy>(fullQuery);
}
auto querySmartTabCompletion(const QString &fullQuery, bool isFirstWord)
{
return queryTabCompletion<SmartTabEmoteStrategy>(fullQuery,
isFirstWord);
}
};
void containsRoughly(std::span<EmoteItem> span, std::set<QString> values)
{
for (const auto &v : values)
{
bool found = false;
for (const auto &actualValue : span)
{
if (actualValue.displayName == v)
{
found = true;
break;
}
}
ASSERT_TRUE(found) << v << " was not found in the span";
}
}
TEST_F(InputCompletionTest, ClassicEmoteNameFiltering)
{
// The completion doesn't guarantee an ordering for a specific category of emotes.
@ -230,9 +263,9 @@ TEST_F(InputCompletionTest, ClassicEmoteNameFiltering)
auto completion = queryClassicEmoteCompletion(":feels");
ASSERT_EQ(completion.size(), 3);
// all these matches are BTTV global emotes
ASSERT_EQ(completion[0].displayName, "FeelsBirthdayMan");
ASSERT_EQ(completion[1].displayName, "FeelsBadMan");
ASSERT_EQ(completion[2].displayName, "FeelsGoodMan");
// these are in no specific order
containsRoughly(completion,
{"FeelsBirthdayMan", "FeelsBadMan", "FeelsGoodMan"});
completion = queryClassicEmoteCompletion(":)");
ASSERT_EQ(completion.size(), 3);
@ -299,6 +332,30 @@ TEST_F(InputCompletionTest, ClassicEmoteProviderOrdering)
ASSERT_EQ(completion[4].providerName, "Emoji");
}
TEST_F(InputCompletionTest, ClassicEmoteCase)
{
auto completion = queryClassicEmoteCompletion(":pajaw");
ASSERT_EQ(completion.size(), 2);
// there's no order here
containsRoughly(completion, {"pajaW", "PAJAW"});
completion = queryClassicEmoteCompletion(":PA");
ASSERT_GT(completion.size(), 3);
containsRoughly({completion.begin(), 2}, {"pajaW", "PAJAW"});
containsRoughly({completion.begin() + 2, completion.end()}, {"parking"});
ASSERT_TRUE(allEmoji({completion.begin() + 2, completion.end()}));
completion = queryClassicEmoteCompletion(":Pajaw");
ASSERT_EQ(completion.size(), 2);
containsRoughly(completion, {"pajaW", "PAJAW"});
completion = queryClassicEmoteCompletion(":NOTHING");
ASSERT_EQ(completion.size(), 0);
completion = queryClassicEmoteCompletion(":nothing");
ASSERT_EQ(completion.size(), 0);
}
TEST_F(InputCompletionTest, ClassicTabCompletionEmote)
{
auto completion = queryClassicTabCompletion(":feels", false);
@ -328,7 +385,13 @@ TEST_F(InputCompletionTest, ClassicTabCompletionEmote)
TEST_F(InputCompletionTest, ClassicTabCompletionEmoji)
{
auto completion = queryClassicTabCompletion(":cla", false);
auto completion = queryClassicTabCompletion(":tf", false);
ASSERT_EQ(completion.size(), 0);
completion = queryClassicTabCompletion(":)", false);
ASSERT_EQ(completion.size(), 0);
completion = queryClassicTabCompletion(":cla", false);
ASSERT_EQ(completion.size(), 8);
ASSERT_EQ(completion[0], ":clap: ");
ASSERT_EQ(completion[1], ":clap_tone1: ");
@ -339,3 +402,177 @@ TEST_F(InputCompletionTest, ClassicTabCompletionEmoji)
ASSERT_EQ(completion[6], ":clapper: ");
ASSERT_EQ(completion[7], ":classical_building: ");
}
TEST_F(InputCompletionTest, ClassicTabCompletionCase)
{
auto completion = queryClassicTabCompletion("pajaw", false);
ASSERT_EQ(completion.size(), 2);
ASSERT_EQ(completion[0], "pajaW ");
ASSERT_EQ(completion[1], "PAJAW ");
completion = queryClassicTabCompletion("PA", false);
ASSERT_EQ(completion.size(), 2);
ASSERT_EQ(completion[0], "pajaW ");
ASSERT_EQ(completion[1], "PAJAW ");
completion = queryClassicTabCompletion("Pajaw", false);
ASSERT_EQ(completion.size(), 2);
ASSERT_EQ(completion[0], "pajaW ");
ASSERT_EQ(completion[1], "PAJAW ");
completion = queryClassicTabCompletion("NOTHING", false);
ASSERT_EQ(completion.size(), 0);
completion = queryClassicTabCompletion("nothing", false);
ASSERT_EQ(completion.size(), 0);
}
TEST_F(InputCompletionTest, SmartEmoteNameFiltering)
{
auto completion = querySmartEmoteCompletion(":feels");
ASSERT_EQ(completion.size(), 3);
ASSERT_EQ(completion[0].displayName, "FeelsBadMan");
ASSERT_EQ(completion[1].displayName, "FeelsGoodMan");
ASSERT_EQ(completion[2].displayName, "FeelsBirthdayMan");
completion = querySmartEmoteCompletion(":)");
ASSERT_EQ(completion.size(), 3);
ASSERT_EQ(completion[0].displayName, ":)");
ASSERT_EQ(completion[1].displayName, ":-)");
ASSERT_EQ(completion[2].displayName, "B-)");
completion = querySmartEmoteCompletion(":cat");
ASSERT_TRUE(completion.size() >= 4);
ASSERT_EQ(completion[0].displayName, "cat");
ASSERT_EQ(completion[1].displayName, "cat2");
ASSERT_EQ(completion[2].displayName, "CatBag");
ASSERT_EQ(completion[3].displayName, "joy_cat");
}
TEST_F(InputCompletionTest, SmartEmoteExactNameMatching)
{
auto completion = querySmartEmoteCompletion(":sal");
ASSERT_TRUE(completion.size() >= 4);
ASSERT_EQ(completion[0].displayName, "salt");
ASSERT_EQ(completion[1].displayName, "SaltyCorn");
ASSERT_EQ(completion[2].displayName, "green_salad");
ASSERT_EQ(completion[3].displayName, "saluting_face");
completion = querySmartEmoteCompletion(":salt");
ASSERT_TRUE(completion.size() >= 2);
ASSERT_EQ(completion[0].displayName, "salt");
ASSERT_EQ(completion[1].displayName, "SaltyCorn");
}
TEST_F(InputCompletionTest, SmartEmoteProviderOrdering)
{
auto completion = querySmartEmoteCompletion(":clap");
ASSERT_TRUE(completion.size() >= 6);
ASSERT_EQ(completion[0].displayName, "clap");
ASSERT_EQ(completion[0].providerName, "Emoji");
ASSERT_EQ(completion[1].displayName, "Clap");
ASSERT_EQ(completion[1].providerName, "Global BetterTTV");
ASSERT_EQ(completion[2].displayName, "Clap");
ASSERT_EQ(completion[2].providerName, "Global 7TV");
ASSERT_EQ(completion[3].displayName, "Clap2");
ASSERT_EQ(completion[3].providerName, "Global 7TV");
ASSERT_EQ(completion[4].displayName, "clapper");
ASSERT_EQ(completion[4].providerName, "Emoji");
ASSERT_EQ(completion[5].displayName, "clap_tone1");
ASSERT_EQ(completion[5].providerName, "Emoji");
}
TEST_F(InputCompletionTest, SmartEmoteCase)
{
auto completion = querySmartEmoteCompletion(":pajaw");
ASSERT_EQ(completion.size(), 2);
ASSERT_EQ(completion[0].displayName, "pajaW");
ASSERT_EQ(completion[1].displayName, "PAJAW");
completion = querySmartEmoteCompletion(":PA");
ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0].displayName, "PAJAW");
completion = querySmartEmoteCompletion(":Pajaw");
ASSERT_EQ(completion.size(), 2);
ASSERT_EQ(completion[0].displayName, "PAJAW");
ASSERT_EQ(completion[1].displayName, "pajaW");
completion = querySmartEmoteCompletion(":NOTHING");
ASSERT_EQ(completion.size(), 0);
completion = querySmartEmoteCompletion(":nothing");
ASSERT_EQ(completion.size(), 0);
}
TEST_F(InputCompletionTest, SmartTabCompletionEmote)
{
auto completion = querySmartTabCompletion(":feels", false);
ASSERT_EQ(completion.size(), 0); // : prefix matters here
// no : prefix defaults to emote completion
completion = querySmartTabCompletion("feels", false);
ASSERT_EQ(completion.size(), 3);
ASSERT_EQ(completion[0], "FeelsBadMan ");
ASSERT_EQ(completion[1], "FeelsGoodMan ");
ASSERT_EQ(completion[2], "FeelsBirthdayMan ");
// no : prefix, emote completion. Duplicate Clap should be removed
completion = querySmartTabCompletion("cla", false);
ASSERT_EQ(completion.size(), 3);
ASSERT_EQ(completion[0], "Clap ");
ASSERT_EQ(completion[1], "Clap ");
ASSERT_EQ(completion[2], "Clap2 ");
completion = querySmartTabCompletion("peepoHappy", false);
ASSERT_EQ(completion.size(), 0);
completion = querySmartTabCompletion("Aware", false);
ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], "Aware ");
}
TEST_F(InputCompletionTest, SmartTabCompletionEmoji)
{
auto completion = querySmartTabCompletion(":tf", false);
ASSERT_EQ(completion.size(), 0);
// ASSERT_EQ(completion[0], ":tf: ");
completion = querySmartTabCompletion(":)", false);
ASSERT_EQ(completion.size(), 0);
// ASSERT_EQ(completion[0], ":) ");
completion = querySmartTabCompletion(":cla", false);
ASSERT_EQ(completion.size(), 8);
ASSERT_EQ(completion[0], ":clap: ");
ASSERT_EQ(completion[1], ":clapper: ");
ASSERT_EQ(completion[2], ":clap_tone1: ");
ASSERT_EQ(completion[3], ":clap_tone2: ");
ASSERT_EQ(completion[4], ":clap_tone3: ");
ASSERT_EQ(completion[5], ":clap_tone4: ");
ASSERT_EQ(completion[6], ":clap_tone5: ");
ASSERT_EQ(completion[7], ":classical_building: ");
}
TEST_F(InputCompletionTest, SmartTabCompletionCase)
{
auto completion = querySmartTabCompletion("pajaw", false);
ASSERT_EQ(completion.size(), 2);
ASSERT_EQ(completion[0], "pajaW ");
ASSERT_EQ(completion[1], "PAJAW ");
completion = querySmartTabCompletion("PA", false);
ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], "PAJAW ");
completion = querySmartTabCompletion("Pajaw", false);
ASSERT_EQ(completion.size(), 2);
ASSERT_EQ(completion[0], "PAJAW ");
ASSERT_EQ(completion[1], "pajaW ");
completion = querySmartTabCompletion("NOTHING", false);
ASSERT_EQ(completion.size(), 0);
completion = querySmartTabCompletion("nothing", false);
ASSERT_EQ(completion.size(), 0);
}