Add a bajillion hypertext parser and translation tests

This commit is contained in:
Alexei Kotov
2026-02-09 02:09:43 +03:00
parent c594a5e0fb
commit 7f20165806
3 changed files with 161 additions and 7 deletions
@@ -1,5 +1,7 @@
#include "apps/openmw/mwdialogue/keywordsearch.hpp"
#include <components/translation/translation.hpp>
#include <gtest/gtest.h>
struct KeywordSearchTest : public ::testing::Test
@@ -181,3 +183,154 @@ TEST_F(KeywordSearchTest, keyword_test_single_char_strings)
EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "a");
EXPECT_EQ(std::string(matches[1].mBeg, matches[1].mEnd), "ab");
}
TEST_F(KeywordSearchTest, parse_hypertext_explicit_links)
{
// Verify that @...# sequences are parsed as explicit links
MWDialogue::KeywordSearch search;
Translation::Storage storage;
std::string text = "Go to @Balmora#.";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 1);
EXPECT_TRUE(matches[0].mExplicit);
EXPECT_EQ(matches[0].mTopicId, "balmora");
EXPECT_EQ(matches[0].getDisplayName(), "Balmora");
std::string matchedText(matches[0].mBeg, matches[0].mEnd);
EXPECT_EQ(matchedText, "@Balmora#");
}
TEST_F(KeywordSearchTest, parse_hypertext_mixed_implicit_and_explicit)
{
// Verify that explicit links and seeded keywords are both returned
MWDialogue::KeywordSearch search;
Translation::Storage storage;
search.seed("guild", "mages guild");
std::string text = "To @join# the guild, talk to the head.";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 2);
EXPECT_TRUE(matches[0].mExplicit);
EXPECT_EQ(matches[0].getDisplayName(), "join");
EXPECT_EQ(matches[0].mTopicId, "join");
EXPECT_FALSE(matches[1].mExplicit);
EXPECT_EQ(matches[1].getDisplayName(), "guild");
EXPECT_EQ(matches[1].mTopicId, "mages guild");
}
TEST_F(KeywordSearchTest, parse_hypertext_keywords_ignored_inside_explicit)
{
// Verify that a seeded keyword is NOT detected if it sits inside an explicit tag
MWDialogue::KeywordSearch search;
Translation::Storage storage;
search.seed("test", "test_id");
std::string text = "This is a @test#.";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 1);
EXPECT_TRUE(matches[0].mExplicit);
EXPECT_EQ(matches[0].mTopicId, "test");
}
TEST_F(KeywordSearchTest, parse_hypertext_broken_tags)
{
// Verify behavior when tags are malformed
MWDialogue::KeywordSearch search;
Translation::Storage storage;
search.seed("text", "text_id");
std::string text = "This is @broken text.";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 1);
EXPECT_FALSE(matches[0].mExplicit);
EXPECT_EQ(matches[0].mTopicId, "text_id");
}
TEST_F(KeywordSearchTest, parse_hypertext_explicit_with_translation)
{
// Verify that standard form conversion works
MWDialogue::KeywordSearch search;
Translation::Storage storage;
storage.addPhraseForm("Балмору", "Балмора");
std::string text = "Поезжайте в @Балмору#.";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 1);
EXPECT_TRUE(matches[0].mExplicit);
EXPECT_EQ(matches[0].getDisplayName(), "Балмору");
EXPECT_EQ(matches[0].mTopicId, "Балмора");
}
TEST_F(KeywordSearchTest, parse_hypertext_explicit_with_pseudo_asterisk_and_translation)
{
// Verify that alternative forms are properly used
MWDialogue::KeywordSearch search;
Translation::Storage storage;
storage.addPhraseForm("Guild", "Fighters Guild");
storage.addPhraseForm("Guild*", "Mages Guild");
std::string text = "Join the @Guild\x7F#.";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 1);
EXPECT_EQ(matches[0].getDisplayName(), "Guild");
EXPECT_EQ(matches[0].mTopicId, "mages guild");
}
TEST_F(KeywordSearchTest, parse_hypertext_preserve_untranslated_links)
{
// Verify that explicit hyperlinks without a standard form are preserved
MWDialogue::KeywordSearch search;
Translation::Storage storage;
storage.addPhraseForm("Двемеры", "Двемер");
std::string text = "@Двемеры# и @данмеры#.";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 2);
EXPECT_EQ(matches[0].getDisplayName(), "Двемеры");
EXPECT_EQ(matches[0].mTopicId, "Двемер");
EXPECT_EQ(matches[1].getDisplayName(), "данмеры");
EXPECT_EQ(matches[1].mTopicId, "данмеры");
}
TEST_F(KeywordSearchTest, parse_hypertext_mark_file_overrides_keyword)
{
// The infamous Bloodmoon test case
// The mark file overrides the keyword that matches the убит topic
MWDialogue::KeywordSearch search;
search.seed("assassinated", "убит");
search.seed("proof", "доказательство");
Translation::Storage storage;
storage.addPhraseForm("доказательств", "доказательство");
storage.addPhraseForm("убит", "убит");
std::string text = "Конечно же, у меня нет @доказательств#, что он был убит or assassinated";
auto matches = search.parseHyperText(text, storage);
ASSERT_EQ(matches.size(), 2);
EXPECT_TRUE(matches[0].mExplicit);
EXPECT_EQ(matches[0].getDisplayName(), "доказательств");
EXPECT_EQ(matches[0].mTopicId, "доказательство");
EXPECT_FALSE(matches[1].mExplicit);
EXPECT_EQ(matches[1].getDisplayName(), "assassinated");
EXPECT_EQ(matches[1].mTopicId, "убит");
}
+5 -5
View File
@@ -95,13 +95,13 @@ namespace Translation
return entry->second;
}
void Storage::addPhraseForm(std::string_view phrase, std::string_view topicId)
{
mPhraseForms.emplace(phrase, topicId);
}
void Storage::setEncoder(ToUTF8::Utf8Encoder* encoder)
{
mEncoder = encoder;
}
bool Storage::hasTranslation() const
{
return !mCellNamesTranslations.empty() || !mKeywords.empty() || !mPhraseForms.empty();
}
}
+3 -2
View File
@@ -21,9 +21,10 @@ namespace Translation
// The phrase that will act as the hyperlink for the given topic ID
std::string_view topicKeyword(std::string_view phrase) const;
void setEncoder(ToUTF8::Utf8Encoder* encoder);
// Manual population for testing
void addPhraseForm(std::string_view phrase, std::string_view topicId);
bool hasTranslation() const;
void setEncoder(ToUTF8::Utf8Encoder* encoder);
private:
typedef std::map<std::string, std::string, std::less<>> ContainerType;