[Card] Bind search language per FilterString instance

The peg parser rules are set up once per process, so the GenericQuery and
OracleQuery rule actions could not capture per-instance state. Instead of
storing the search language in a process-global that FilterString instance
methods mutate, hand it to the rule actions through a thread-local parse
context and copy it into the filter closures they produce. Card evaluation
in FilterString::check no longer reads any process-global state, and each
instance keeps the language it was built with; constructing one instance no
longer changes what unrelated instances (deck filter, drop-to-hand, zone
views) match against.

The card database display model stores the raw query and rebuilds the
FilterString when the search language changes, since the language is now
bound at parse time.

Add tests for the English/Selected/Both search modes, the English fallback
for untranslated cards, and per-instance language independence.
This commit is contained in:
Lukas Brübach 2026-09-19 10:47:24 +02:00
parent e029173e29
commit 14cf4ee2a9
5 changed files with 81 additions and 33 deletions

View file

@ -73,6 +73,57 @@ QUERY(Color4, cat, "c!gw", false)
QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true)
CardInfoPtr localizedCat()
{
CardInfoPtr localized = CardInfo::newInstance("Cat", "Meow!", false, {}, {}, {}, {}, {});
localized->setLocalizedName("de", "Kater");
localized->setLocalizedText("de", "miaut");
return localized;
}
TEST_F(CardQuery, SearchLanguageEnglishMatchesOnlyEnglish)
{
const CardData localized = localizedCat();
ASSERT_TRUE(FilterString("Cat", "de", CardSearchLanguage::English).check(localized));
ASSERT_FALSE(FilterString("Kater", "de", CardSearchLanguage::English).check(localized));
}
TEST_F(CardQuery, SearchLanguageSelectedMatchesLocalizedNameAndText)
{
const CardData localized = localizedCat();
ASSERT_TRUE(FilterString("Kater", "de", CardSearchLanguage::Selected).check(localized));
ASSERT_TRUE(FilterString("o:miaut", "de", CardSearchLanguage::Selected).check(localized));
ASSERT_FALSE(FilterString("Cat", "de", CardSearchLanguage::Selected).check(localized));
}
TEST_F(CardQuery, SearchLanguageSelectedFallsBackToEnglishForUntranslatedCards)
{
const CardData localized = localizedCat();
ASSERT_TRUE(FilterString("Cat", "fr", CardSearchLanguage::Selected).check(localized));
ASSERT_FALSE(FilterString("Kater", "fr", CardSearchLanguage::Selected).check(localized));
}
TEST_F(CardQuery, SearchLanguageBothMatchesEitherLanguage)
{
const CardData localized = localizedCat();
ASSERT_TRUE(FilterString("Cat", "de", CardSearchLanguage::Both).check(localized));
ASSERT_TRUE(FilterString("Kater", "de", CardSearchLanguage::Both).check(localized));
}
TEST_F(CardQuery, SearchLanguageIsBoundPerInstance)
{
const CardData localized = localizedCat();
FilterString germanQuery("Kater", "de", CardSearchLanguage::Selected);
ASSERT_TRUE(germanQuery.check(localized));
// Constructing an English-bound instance afterwards must not change the
// language the earlier instance searches in.
FilterString englishQuery("Kater", "", CardSearchLanguage::English);
ASSERT_FALSE(englishQuery.check(localized));
ASSERT_TRUE(germanQuery.check(localized));
}
} // namespace
int main(int argc, char **argv)