Files
msime-windows/server/tests/src/test_engine_shuangpin_session.cpp
T

702 lines
28 KiB
C++

#include "tests/includes/test_framework.h"
#include "src/session/engine_input_session.h"
#include "MetasequoiaImeEngine/common/helpcode_utils.h"
#include "MetasequoiaImeEngine/quanpin/quanpin_query.h"
#include "MetasequoiaImeEngine/shuangpin/shuangpin_dictionary.h"
#include "src/ipc/candidate_selection_policy.h"
#include <algorithm>
namespace
{
void InputLetters(EngineInputSession &session, const std::string &keys)
{
for (const char ch : keys)
{
const bool is_upper = ch >= 'A' && ch <= 'Z';
const char upper = is_upper ? ch : static_cast<char>(ch - ('a' - 'A'));
session.handle_key(static_cast<UINT>(upper), 0, static_cast<WCHAR>(ch));
}
}
void InputSequence(EngineInputSession &session, const std::string &keys)
{
for (const char ch : keys)
{
if (ch == '\'')
{
session.handle_key(VK_OEM_7, 0, L'\'');
continue;
}
const bool is_upper = ch >= 'A' && ch <= 'Z';
const char upper = is_upper ? ch : static_cast<char>(ch - ('a' - 'A'));
session.handle_key(static_cast<UINT>(upper), 0, static_cast<WCHAR>(ch));
}
}
}
TEST_CASE(EngineShuangpinSessionContinuesCompositionWithoutHelpcode)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xitele");
const auto transition = session.advance_composition_after_selection("xi", "西", "xi");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("tele"));
}
TEST_CASE(CloudCandidateNeverEntersCreatingWordMode)
{
REQUIRE(!FanyImeIpc::ShouldEnterCreatingWord(CandidateSource::CloudSuggestion, true));
REQUIRE(!FanyImeIpc::ShouldEnterCreatingWord(CandidateSource::CloudSuggestion, false));
REQUIRE(FanyImeIpc::ShouldEnterCreatingWord(CandidateSource::Database, true));
REQUIRE(!FanyImeIpc::ShouldEnterCreatingWord(CandidateSource::Database, false));
}
TEST_CASE(MixedAsyncCandidatesKeepReservedSlotsForEveryArrivalOrder)
{
const auto local = [](std::string word) { return WordItem("ni", std::move(word), 100); };
const auto english = [] { return WordItem("ni", "nice", 1, CandidateSource::EnglishDictionary); };
const auto emoji = [] { return WordItem("ni", "\xF0\x9F\x98\x80", 1, CandidateSource::Emoji); };
const auto cloud = [] { return WordItem("ni", "云候选", 1, CandidateSource::CloudSuggestion); };
const auto ai = [] { return WordItem("ni", "AI联想", 1, CandidateSource::AiSuggestion); };
std::vector<WordItem> items = {local("你"), english(), local("呢"), ai(), cloud()};
FanyImeIpc::NormalizeMixedCandidateOrder(items);
REQUIRE_EQ(items[0].word, std::string("你"));
REQUIRE_EQ(items[1].source, CandidateSource::CloudSuggestion);
REQUIRE_EQ(items[2].source, CandidateSource::AiSuggestion);
REQUIRE_EQ(items[3].source, CandidateSource::EnglishDictionary);
items = {local("你"), ai(), local("呢"), english()};
FanyImeIpc::NormalizeMixedCandidateOrder(items);
REQUIRE_EQ(items[1].source, CandidateSource::EnglishDictionary);
REQUIRE_EQ(items[2].source, CandidateSource::AiSuggestion);
items = {local("你"), cloud(), local("呢"), english()};
FanyImeIpc::NormalizeMixedCandidateOrder(items);
REQUIRE_EQ(items[1].source, CandidateSource::CloudSuggestion);
REQUIRE_EQ(items[2].source, CandidateSource::EnglishDictionary);
}
TEST_CASE(EmojiMixedCandidateFollowsEnglishAndShiftsWithCloudAndAi)
{
const auto local = [](std::string word) { return WordItem("ni", std::move(word), 100); };
const auto english = [] { return WordItem("ni", "nice", 1, CandidateSource::EnglishDictionary); };
const auto emoji = [] { return WordItem("ni", "\xF0\x9F\x98\x80", 1, CandidateSource::Emoji); };
const auto cloud = [] { return WordItem("ni", "云候选", 1, CandidateSource::CloudSuggestion); };
const auto ai = [] { return WordItem("ni", "AI联想", 1, CandidateSource::AiSuggestion); };
// Base case: English at slot 2, emoji at slot 3.
std::vector<WordItem> items = {local("你"), emoji(), local("呢"), english()};
FanyImeIpc::NormalizeMixedCandidateOrder(items);
REQUIRE_EQ(items[1].source, CandidateSource::EnglishDictionary);
REQUIRE_EQ(items[2].source, CandidateSource::Emoji);
// Cloud + AI occupy slots 2/3; English and emoji shift to slots 4/5.
items = {local("你"), emoji(), english(), ai(), cloud()};
FanyImeIpc::NormalizeMixedCandidateOrder(items);
REQUIRE_EQ(items[1].source, CandidateSource::CloudSuggestion);
REQUIRE_EQ(items[2].source, CandidateSource::AiSuggestion);
REQUIRE_EQ(items[3].source, CandidateSource::EnglishDictionary);
REQUIRE_EQ(items[4].source, CandidateSource::Emoji);
// Cloud only: English slot 3, emoji slot 4.
items = {local("你"), emoji(), local("呢"), english(), cloud()};
FanyImeIpc::NormalizeMixedCandidateOrder(items);
REQUIRE_EQ(items[1].source, CandidateSource::CloudSuggestion);
REQUIRE_EQ(items[2].source, CandidateSource::EnglishDictionary);
REQUIRE_EQ(items[3].source, CandidateSource::Emoji);
}
TEST_CASE(EngineShuangpinAiCandidateConsumesFullRawInput)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "geziaa");
const auto transition = session.advance_composition_after_selection("geziaa", "鸽子啊", "ge'zi'a");
REQUIRE(!transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("geziaa"));
}
TEST_CASE(EngineShuangpinSessionContinuesCompositionWithSingleHelpcode)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xitelea");
const auto transition = session.advance_composition_after_selection("xi", "西", "xi");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("tele"));
}
TEST_CASE(EngineShuangpinUppercaseSingleHelpcodePrefersSecondCodeMatches)
{
REQUIRE(HelpcodeUtils::select_helpcode_schema("lantian"));
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "nid");
session.reset_state();
InputLetters(session, "niD");
const auto &candidates = session.get_candidates();
const auto index_of = [&](const std::string &word) {
const auto found = std::find_if(candidates.begin(), candidates.end(),
[&](const IInputSession::WordItem &item) { return item.word == word; });
return static_cast<size_t>(std::distance(candidates.begin(), found));
};
const size_t ni = index_of("泥");
const size_t ni_second_code = index_of("溺");
const size_t ni_other_second_code = index_of("腻");
REQUIRE(ni < candidates.size());
REQUIRE(ni_second_code < candidates.size());
REQUIRE(ni_other_second_code < candidates.size());
REQUIRE(ni_second_code < ni);
REQUIRE(ni_other_second_code < ni);
}
TEST_CASE(EngineShuangpinSessionContinuesCompositionWithDoubleHelpcode)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xiteleaA");
const auto transition = session.advance_composition_after_selection("xi", "西", "xi");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("tele"));
}
TEST_CASE(EngineShuangpinDoubleHelpcodesAreDisplayedAsOneSegment)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "yakP");
REQUIRE_EQ(session.get_pinyin_segmentation_with_cases(), std::string("ya'kP"));
}
TEST_CASE(EngineShuangpinSessionCloudQueryMatchesLegacyTiming)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xi");
auto state = session.get_cloud_query_state();
REQUIRE(state.should_query);
session.reset_state();
InputLetters(session, "xia");
state = session.get_cloud_query_state();
REQUIRE(!state.should_query);
session.reset_state();
InputLetters(session, "xiA");
state = session.get_cloud_query_state();
REQUIRE(!state.should_query);
REQUIRE_EQ(state.cache_key, std::string("xi"));
session.reset_state();
InputLetters(session, "xI");
state = session.get_cloud_query_state();
REQUIRE(!state.should_query);
}
TEST_CASE(EngineShuangpinSessionCloudQueryDoesNotTriggerWhenHelpcodesApply)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xitelea");
auto state = session.get_cloud_query_state();
REQUIRE(!state.should_query);
REQUIRE_EQ(state.cache_key, std::string("xitele"));
session.reset_state();
InputLetters(session, "xiteleaA");
state = session.get_cloud_query_state();
REQUIRE(!state.should_query);
REQUIRE_EQ(state.cache_key, std::string("xitele"));
}
TEST_CASE(EngineShuangpinSessionCloudCommitUsesRawShuangpinSequence)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "vh");
const auto state = session.get_cloud_query_state();
REQUIRE(state.should_query);
REQUIRE_EQ(state.query_text, std::string("zhang"));
REQUIRE_EQ(state.cache_key, std::string("vh"));
REQUIRE_EQ(state.committed_pinyin, std::string("vh"));
}
TEST_CASE(EngineShuangpinSessionStoresMultiSyllableDynamicPhrase)
{
EngineInputSession session(SchemeType::Shuangpin);
// Cloud and AI candidates are committed with their raw shuangpin sequence.
// "vsgo" must retain the converted boundary "zhong'guo" before entering
// the strict canonical-pinyin storage path. "中国" already exists in the
// shipped dictionary, so this verifies the route without mutating user data.
REQUIRE_EQ(session.store_user_phrase("vsgo", "中国"), 0);
}
TEST_CASE(EngineQuanpinSessionCloudQueryDoesNotTriggerWhenHelpcodesApply)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "xiteleA");
auto state = session.get_cloud_query_state();
REQUIRE(!state.should_query);
REQUIRE_EQ(state.cache_key, std::string("xitele"));
session.reset_state();
InputLetters(session, "xiteleAA");
state = session.get_cloud_query_state();
REQUIRE(!state.should_query);
REQUIRE_EQ(state.cache_key, std::string("xitele"));
session.reset_state();
InputLetters(session, "xitelR");
state = session.get_cloud_query_state();
REQUIRE(state.should_query);
}
TEST_CASE(EngineQuanpinSessionContinuesCompositionForCreatingWord)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "xitele");
const auto transition = session.advance_composition_after_selection("xi", "西", "xi");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("tele"));
REQUIRE_EQ(session.get_pinyin_segmentation_with_cases(), std::string("te'le"));
}
TEST_CASE(EngineQuanpinSessionCompletesCreatingWordProgress)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "xitele");
const auto first_transition = session.advance_composition_after_selection("xi", "西", "xi");
const auto first_progress = session.update_creating_word_progress("", "", "西", first_transition);
REQUIRE(!first_progress.completed);
REQUIRE_EQ(first_progress.pinyin, std::string("xi"));
REQUIRE_EQ(first_progress.word, std::string("西"));
REQUIRE_EQ(first_progress.preedit, std::string("西te'le"));
const auto second_transition =
session.advance_composition_after_selection("te'le", "特乐", "te'le");
REQUIRE(!second_transition.continues_composition);
const auto second_progress =
session.update_creating_word_progress(first_progress.pinyin, first_progress.word, "特乐", second_transition);
REQUIRE(second_progress.completed);
REQUIRE(second_progress.can_store);
REQUIRE_EQ(second_progress.pinyin, std::string("xi'te'le"));
REQUIRE_EQ(second_progress.word, std::string("西特乐"));
}
TEST_CASE(EngineQuanpinAbbreviationsContinueAndCreateWithCanonicalPinyin)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "zgrm");
const auto first =
session.advance_composition_after_selection("z'g", "中国", "zhong'guo");
REQUIRE(first.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("rm"));
REQUIRE_EQ(session.get_pinyin_segmentation_with_cases(), std::string("r'm"));
const auto first_progress =
session.update_creating_word_progress("", "", "中国", first);
REQUIRE(!first_progress.completed);
REQUIRE_EQ(first_progress.pinyin, std::string("zhong'guo"));
REQUIRE_EQ(first_progress.word, std::string("中国"));
const auto second =
session.advance_composition_after_selection("r'm", "人民", "ren'min");
REQUIRE(!second.continues_composition);
const auto completed = session.update_creating_word_progress(
first_progress.pinyin, first_progress.word, "人民", second);
REQUIRE(completed.completed);
REQUIRE(completed.can_store);
REQUIRE_EQ(completed.pinyin, std::string("zhong'guo'ren'min"));
REQUIRE_EQ(completed.word, std::string("中国人民"));
}
TEST_CASE(EngineQuanpinAbbreviationCandidatesRetainDatabaseCanonicalPinyin)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "zgrm");
const auto &candidates = session.get_candidates();
const auto candidate = std::find_if(candidates.begin(), candidates.end(), [](const IInputSession::WordItem &item) {
return item.source == CandidateSource::Database && item.pinyin != item.canonical_pinyin;
});
REQUIRE(candidate != candidates.end());
REQUIRE(!candidate->canonical_pinyin.empty());
REQUIRE_EQ(quanpin::split_segments(candidate->canonical_pinyin).size(),
HelpcodeUtils::count_han_chars(candidate->word));
}
TEST_CASE(EngineQuanpinSelectionPreservesManualSeparatorsInRemainingInput)
{
EngineInputSession session(SchemeType::Quanpin);
InputSequence(session, "zgrm'gh");
const auto first =
session.advance_composition_after_selection("z'g", "中国", "zhong'guo");
REQUIRE(first.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("rm'gh"));
REQUIRE_EQ(session.get_pinyin_sequence_with_cases(), std::string("rm'gh"));
const auto second =
session.advance_composition_after_selection("r'm", "人民", "ren'min");
REQUIRE(second.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("gh"));
}
TEST_CASE(EngineQuanpinSelectionConsumesOnlyTheLeadingManualBoundary)
{
EngineInputSession session(SchemeType::Quanpin);
InputSequence(session, "zg'rm'gh");
const auto transition =
session.advance_composition_after_selection("z'g", "中国", "zhong'guo");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("rm'gh"));
}
TEST_CASE(EngineShuangpinIncompleteManualSegmentsContinueCreatingWord)
{
EngineInputSession session(SchemeType::Shuangpin);
InputSequence(session, "v'x'r'm");
const auto first =
session.advance_composition_after_selection("v", "中", "zhong");
REQUIRE(first.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("x'r'm"));
const auto first_progress =
session.update_creating_word_progress("", "", "中", first);
REQUIRE(!first_progress.completed);
REQUIRE_EQ(first_progress.pinyin, std::string("zhong"));
REQUIRE_EQ(first_progress.word, std::string("中"));
const auto second =
session.advance_composition_after_selection("x", "西", "xi");
REQUIRE(second.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("r'm"));
const auto second_progress = session.update_creating_word_progress(
first_progress.pinyin, first_progress.word, "西", second);
const auto third =
session.advance_composition_after_selection("r", "人", "ren");
REQUIRE(third.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("m"));
const auto third_progress = session.update_creating_word_progress(
second_progress.pinyin, second_progress.word, "人", third);
const auto fourth =
session.advance_composition_after_selection("m", "民", "min");
REQUIRE(!fourth.continues_composition);
const auto completed = session.update_creating_word_progress(
third_progress.pinyin, third_progress.word, "民", fourth);
REQUIRE(completed.completed);
REQUIRE(completed.can_store);
REQUIRE_EQ(completed.pinyin, std::string("zhong'xi'ren'min"));
REQUIRE_EQ(completed.word, std::string("中西人民"));
}
TEST_CASE(EngineQuanpinIncompleteUppercaseSuffixIsNotConsumedAsHelpcode)
{
EngineInputSession session(SchemeType::Quanpin);
InputSequence(session, "zgR");
const auto transition =
session.advance_composition_after_selection("z", "中", "zhong");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("gr"));
REQUIRE_EQ(session.get_pinyin_sequence_with_cases(), std::string("gR"));
}
TEST_CASE(EngineQuanpinCompleteHelpcodeIsDiscardedButManualRemainderIsPreserved)
{
EngineInputSession session(SchemeType::Quanpin);
InputSequence(session, "ni'shuo'neNV");
const auto transition =
session.advance_composition_after_selection("ni'shuo", "你说", "ni'shuo");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("ne"));
REQUIRE_EQ(session.get_pinyin_sequence_with_cases(), std::string("ne"));
}
TEST_CASE(EngineCreatingWordDoesNotStoreWhenAnySelectedPartLacksCanonicalPinyin)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "zgrm");
const auto first = session.advance_composition_after_selection("z'g", "中国", "");
REQUIRE(first.continues_composition);
const auto first_progress =
session.update_creating_word_progress("", "", "中国", first);
REQUIRE(first_progress.pinyin.empty());
const auto second =
session.advance_composition_after_selection("r'm", "人民", "ren'min");
const auto completed = session.update_creating_word_progress(
first_progress.pinyin, first_progress.word, "人民", second);
REQUIRE(completed.completed);
REQUIRE(!completed.can_store);
REQUIRE(completed.pinyin.empty());
}
TEST_CASE(EngineQuanpinSessionContinuesCompositionAfterMultiSyllableSelection)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "zhengxianghuafen");
const auto transition =
session.advance_composition_after_selection("zheng'xiang", "正向", "zheng'xiang");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("huafen"));
REQUIRE_EQ(session.get_pinyin_segmentation_with_cases(), std::string("hua'fen"));
const auto progress = session.update_creating_word_progress("", "", "正向", transition);
REQUIRE_EQ(progress.pinyin, std::string("zheng'xiang"));
REQUIRE_EQ(progress.word, std::string("正向"));
REQUIRE_EQ(progress.preedit, std::string("正向hua'fen"));
}
TEST_CASE(EngineQuanpinSessionContinuesCompositionWithoutRetainingHelpcodes)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "nishuoneNV");
const auto transition =
session.advance_composition_after_selection("ni'shuo", "你说", "ni'shuo");
REQUIRE(transition.continues_composition);
REQUIRE_EQ(session.get_pinyin_sequence(), std::string("ne"));
REQUIRE_EQ(session.get_pinyin_sequence_with_cases(), std::string("ne"));
REQUIRE_EQ(session.get_pinyin_segmentation_with_cases(), std::string("ne"));
const auto progress = session.update_creating_word_progress("", "", "你说", transition);
REQUIRE_EQ(progress.pinyin, std::string("ni'shuo"));
REQUIRE_EQ(progress.word, std::string("你说"));
REQUIRE_EQ(progress.preedit, std::string("你说ne"));
}
TEST_CASE(EngineShuangpinSessionDynamicCloudCandidateParticipatesInHelpcodesQuery)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xitele");
const auto state = session.get_cloud_query_state();
REQUIRE(state.should_query);
session.cache_dynamic_candidate(state.cache_key, "云词", CandidateSource::CloudSuggestion);
InputLetters(session, "a");
const auto &candidates = session.get_candidates();
const auto found = std::find_if(candidates.begin(), candidates.end(), [](const IInputSession::WordItem &item) {
return item.word == "云词";
});
REQUIRE(found != candidates.end());
REQUIRE(found->source == CandidateSource::CloudSuggestion);
}
TEST_CASE(EngineShuangpinSessionDynamicCandidateCacheDedupesRepeatedInserts)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xitele");
const auto state = session.get_cloud_query_state();
REQUIRE(state.should_query);
session.cache_dynamic_candidate(state.cache_key, "云词", CandidateSource::CloudSuggestion);
session.cache_dynamic_candidate(state.cache_key, "云词", CandidateSource::CloudSuggestion);
session.cache_dynamic_candidate(state.cache_key, "AI词", CandidateSource::AiSuggestion);
session.cache_dynamic_candidate(state.cache_key, "AI词", CandidateSource::AiSuggestion);
session.cache_dynamic_candidate(state.cache_key, "AI词2", CandidateSource::AiSuggestion);
// Force a fresh query so candidates are rebuilt from the series cache.
session.handle_key(VK_BACK, 0, 0);
InputLetters(session, "e");
const auto &candidates = session.get_candidates();
const auto cloud_count = std::count_if(candidates.begin(), candidates.end(), [](const IInputSession::WordItem &item) {
return item.source == CandidateSource::CloudSuggestion && item.word == "云词";
});
const auto ai_count = std::count_if(candidates.begin(), candidates.end(), [](const IInputSession::WordItem &item) {
return item.source == CandidateSource::AiSuggestion;
});
const auto ai_latest = std::count_if(candidates.begin(), candidates.end(), [](const IInputSession::WordItem &item) {
return item.source == CandidateSource::AiSuggestion && item.word == "AI词2";
});
const auto ai_stale = std::count_if(candidates.begin(), candidates.end(), [](const IInputSession::WordItem &item) {
return item.source == CandidateSource::AiSuggestion && item.word == "AI词";
});
REQUIRE_EQ(cloud_count, 1);
REQUIRE_EQ(ai_count, 1);
REQUIRE_EQ(ai_latest, 1);
REQUIRE_EQ(ai_stale, 0);
}
TEST_CASE(EngineQuanpinSessionDynamicCloudCandidateParticipatesInHelpcodesQuery)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "xitele");
const auto state = session.get_cloud_query_state();
REQUIRE(state.should_query);
session.cache_dynamic_candidate(state.cache_key, "云词", CandidateSource::CloudSuggestion);
InputLetters(session, "A");
const auto &candidates = session.get_candidates();
const auto found = std::find_if(candidates.begin(), candidates.end(), [](const IInputSession::WordItem &item) {
return item.word == "云词";
});
REQUIRE(found != candidates.end());
REQUIRE(found->source == CandidateSource::CloudSuggestion);
}
TEST_CASE(EngineShuangpinSessionHasActiveHelpcodeTracksCloudQueryGate)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "xitele");
REQUIRE(session.is_all_complete_pure_pinyin());
REQUIRE(!session.has_active_helpcode());
REQUIRE(session.get_cloud_query_state().should_query);
InputLetters(session, "a");
REQUIRE(session.is_all_complete_pure_pinyin());
REQUIRE(session.has_active_helpcode());
REQUIRE(!session.get_cloud_query_state().should_query);
}
TEST_CASE(EngineQuanpinSessionHasActiveHelpcodeTracksCloudQueryGate)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "xitele");
REQUIRE(session.is_all_complete_pure_pinyin());
REQUIRE(!session.has_active_helpcode());
REQUIRE(session.get_cloud_query_state().should_query);
InputLetters(session, "A");
REQUIRE(session.is_all_complete_pure_pinyin());
REQUIRE(session.has_active_helpcode());
REQUIRE(!session.get_cloud_query_state().should_query);
}
TEST_CASE(EngineShuangpinSessionUsesZiranmaProfileEndToEnd)
{
EngineInputSession session(SchemeType::Shuangpin, GetZiranmaShuangpinProfile());
InputLetters(session, "xd");
const auto state = session.get_cloud_query_state();
REQUIRE(state.should_query);
REQUIRE_EQ(state.query_text, std::string("xiang"));
REQUIRE_EQ(session.get_quanpin(), std::string("xiang"));
}
TEST_CASE(LegacyShuangpinDictionaryUsesZiranmaProfile)
{
ShuangpinDictionary dictionary(GetZiranmaShuangpinProfile());
dictionary.handleVkCode('X', 0, L'x');
dictionary.handleVkCode('D', 0, L'd');
REQUIRE_EQ(dictionary.get_quanpin(), std::string("xiang"));
}
TEST_CASE(EngineShuangpinInitialVQueriesZhCandidatesAndExpands)
{
EngineInputSession session(SchemeType::Shuangpin);
InputLetters(session, "v");
const auto &initial_candidates = session.get_candidates();
REQUIRE_EQ(initial_candidates.size(), static_cast<std::size_t>(24));
REQUIRE(std::all_of(initial_candidates.begin(), initial_candidates.end(), [](const IInputSession::WordItem &item) {
return item.pinyin == "v" && item.canonical_pinyin.rfind("zh", 0) == 0;
}));
const auto initial_count = initial_candidates.size();
REQUIRE(session.expand_initial_candidates());
const auto &expanded_candidates = session.get_candidates();
REQUIRE(expanded_candidates.size() > initial_count);
REQUIRE(std::all_of(expanded_candidates.begin(), expanded_candidates.end(), [](const IInputSession::WordItem &item) {
return item.pinyin == "v" && item.canonical_pinyin.rfind("zh", 0) == 0;
}));
}
TEST_CASE(EngineQuanpinInitialCandidatesStartLimitedAndExpand)
{
EngineInputSession session(SchemeType::Quanpin);
InputLetters(session, "z");
const auto &initial_candidates = session.get_candidates();
REQUIRE_EQ(initial_candidates.size(), static_cast<std::size_t>(24));
REQUIRE(std::all_of(initial_candidates.begin(), initial_candidates.end(), [](const IInputSession::WordItem &item) {
return item.pinyin.rfind("z", 0) == 0;
}));
const auto initial_count = initial_candidates.size();
REQUIRE(session.expand_initial_candidates());
const auto &expanded_candidates = session.get_candidates();
REQUIRE(expanded_candidates.size() > initial_count);
}
TEST_CASE(EngineQuanpinSegmentedInitialCandidatesExpandInPlace)
{
EngineInputSession session(SchemeType::Quanpin);
InputSequence(session, "l'shi");
const auto count_initials = [](const std::vector<IInputSession::WordItem> &items) {
return std::count_if(items.begin(), items.end(), [](const IInputSession::WordItem &item) {
return item.source == CandidateSource::Database && item.pinyin == "l";
});
};
const auto count_other_candidates = [](const std::vector<IInputSession::WordItem> &items) {
return std::count_if(items.begin(), items.end(), [](const IInputSession::WordItem &item) {
return item.source != CandidateSource::Database || item.pinyin != "l";
});
};
const auto initial_candidates = session.get_candidates();
REQUIRE_EQ(count_initials(initial_candidates), 24);
const auto other_count = count_other_candidates(initial_candidates);
REQUIRE(session.expand_initial_candidates());
const auto &expanded_candidates = session.get_candidates();
REQUIRE(count_initials(expanded_candidates) > 24);
REQUIRE_EQ(count_other_candidates(expanded_candidates), other_count);
}
TEST_CASE(EngineShuangpinSegmentedInitialCandidatesExpandInPlace)
{
EngineInputSession session(SchemeType::Shuangpin);
InputSequence(session, "v'ui");
const auto count_initials = [](const std::vector<IInputSession::WordItem> &items) {
return std::count_if(items.begin(), items.end(), [](const IInputSession::WordItem &item) {
return item.source == CandidateSource::Database && item.pinyin == "v";
});
};
const auto count_other_candidates = [](const std::vector<IInputSession::WordItem> &items) {
return std::count_if(items.begin(), items.end(), [](const IInputSession::WordItem &item) {
return item.source != CandidateSource::Database || item.pinyin != "v";
});
};
const auto initial_candidates = session.get_candidates();
REQUIRE_EQ(count_initials(initial_candidates), 24);
const auto other_count = count_other_candidates(initial_candidates);
REQUIRE(session.expand_initial_candidates());
const auto &expanded_candidates = session.get_candidates();
REQUIRE(count_initials(expanded_candidates) > 24);
REQUIRE_EQ(count_other_candidates(expanded_candidates), other_count);
}