Most of them come from logging that is compiled out. The disabled DIAG_LOGF / FANY_IPC_LOG* macros expanded to `((void)0)`, so every HRESULT and parameter that exists only to be logged looked unreferenced (C4189/C4100). They now expand to `((void)sizeof(DiscardLogArgs(__VA_ARGS__)))`: the arguments stay in an unevaluated context, so nothing is computed and no side effect runs, but the names count as used. The rest: - C4100: commented out the parameter names that are genuinely unused (WinMain's hPrevInstance/nCmdShow, AdjustWndPosition's hwnd/height, the WebView2 callbacks' sender, and the no-op logging sinks). - C4189/C4101: removed the dead locals and the unused catch bindings left over with the log statements. - C4244: made the DIP-to-pixel and int-to-float conversions explicit in globals.h, ime_windows.cpp and the menu placement. - C4456: renamed the shadowing `hwnd` and `lock` locals. - C4005: guarded the tests' `#define NOMINMAX` with #ifndef; the test targets already define it on the command line. Release builds clean and ctest passes for the server, the tests and msimeui. Warnings under vendor/ are untouched: those are submodules with their own repositories.
186 lines
9.5 KiB
C++
186 lines
9.5 KiB
C++
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include "config/ime_config.h"
|
|
#include "tests/includes/test_framework.h"
|
|
#include <type_traits>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
|
|
TEST_CASE(font_fallback_upgrade_preserves_legacy_fonts_and_order)
|
|
{
|
|
const std::string next = "[appearance]\nfallback_fonts = [\"Noto Sans SC\", \"Microsoft YaHei\"]\n";
|
|
const std::string old = "[appearance]\nfont = \"SimSun\"\ndefault_font = \"DengXian\"\n";
|
|
const auto migrated = MergeConfigIntoTemplate(next, old, old);
|
|
REQUIRE(migrated.find("[\"SimSun\", \"DengXian\"]") != std::string::npos);
|
|
const std::string custom = "[appearance]\nfallback_fonts = [\n \"Font#1\", # comment\n \"Font]2\"\n]\n";
|
|
REQUIRE(MergeConfigIntoTemplate(next, custom, next).find("Font]2") != std::string::npos);
|
|
REQUIRE(MergeConfigIntoTemplate(next, "[appearance]\nfallback_fonts = []\n", next).find("fallback_fonts = []") !=
|
|
std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(shortcut_config_upgrade_preserves_word_selection_and_adds_defaults)
|
|
{
|
|
const std::string old_config =
|
|
"[input]\nword_to_character = true\n\n[keybindings]\nswitch_language_shift = false\n";
|
|
const std::string new_template =
|
|
"[input]\nword_to_character = false\nword_to_character_keys = \"brackets\"\n\n"
|
|
"[keybindings]\nswitch_language_shift = true\ntoggle_character_set_ctrl_shift_f = true\n";
|
|
const auto merged = MergeConfigIntoTemplate(new_template, old_config, "");
|
|
REQUIRE(merged.find("word_to_character = true") != std::string::npos);
|
|
REQUIRE(merged.find("word_to_character_keys = \"brackets\"") != std::string::npos);
|
|
REQUIRE(merged.find("switch_language_shift = false") != std::string::npos);
|
|
REQUIRE(merged.find("toggle_character_set_ctrl_shift_f = true") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(candidate_key_config_rejects_invalid_groups_without_changing_state)
|
|
{
|
|
const auto keys = GetConfiguredWordToCharacterKeys();
|
|
const bool enabled = GetConfiguredWordToCharacterEnabled();
|
|
const bool brackets = GetConfiguredPagingBracketsEnabled();
|
|
const bool minus = GetConfiguredPagingMinusEqualEnabled();
|
|
REQUIRE(!SetConfiguredWordToCharacterKeys("unsupported"));
|
|
REQUIRE_EQ(GetConfiguredWordToCharacterKeys(), keys);
|
|
REQUIRE_EQ(GetConfiguredWordToCharacterEnabled(), enabled);
|
|
REQUIRE_EQ(GetConfiguredPagingBracketsEnabled(), brackets);
|
|
REQUIRE_EQ(GetConfiguredPagingMinusEqualEnabled(), minus);
|
|
}
|
|
|
|
TEST_CASE(config_merge_keeps_customized_values_and_adds_new_keys)
|
|
{
|
|
const std::string template_text = "[general]\n"
|
|
"# 悬浮工具栏\n"
|
|
"floating_toolbar = true\n"
|
|
"cloud_candidates = true\n"
|
|
"\n"
|
|
"[input]\n"
|
|
"schema = \"shuangpin\" # 可选:quanpin/shuangpin\n";
|
|
const std::string user_text = "[general]\n"
|
|
"floating_toolbar = false\n"
|
|
"\n"
|
|
"[input]\n"
|
|
"schema = \"quanpin\"\n";
|
|
const std::string baseline_text = "[general]\n"
|
|
"floating_toolbar = true\n"
|
|
"\n"
|
|
"[input]\n"
|
|
"schema = \"shuangpin\"\n";
|
|
|
|
const std::string expected = "[general]\n"
|
|
"# 悬浮工具栏\n"
|
|
"floating_toolbar = false\n"
|
|
"cloud_candidates = true\n"
|
|
"\n"
|
|
"[input]\n"
|
|
"schema = \"quanpin\" # 可选:quanpin/shuangpin\n";
|
|
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, baseline_text), expected);
|
|
}
|
|
|
|
TEST_CASE(config_merge_lets_new_defaults_win_for_untouched_keys)
|
|
{
|
|
const std::string template_text = "[appearance]\npage_size = 9\nfont_size = 18\n";
|
|
const std::string user_text = "[appearance]\npage_size = 8\nfont_size = 20\n";
|
|
const std::string baseline_text = "[appearance]\npage_size = 8\nfont_size = 16\n";
|
|
|
|
// page_size 仍是旧默认值 -> 跟随新默认值;font_size 被用户改过 -> 保留。
|
|
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, baseline_text),
|
|
"[appearance]\npage_size = 9\nfont_size = 20\n");
|
|
}
|
|
|
|
TEST_CASE(config_merge_drops_keys_and_sections_absent_from_template)
|
|
{
|
|
const std::string template_text = "[general]\nkept = true\n";
|
|
const std::string user_text = "[general]\nkept = false\nretired = 1\n\n[gone]\nvalue = \"x\"\n";
|
|
|
|
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()), "[general]\nkept = false\n");
|
|
}
|
|
|
|
TEST_CASE(config_merge_keeps_all_user_values_without_a_baseline)
|
|
{
|
|
// 从没有 config.base.toml 的旧版本升级时,无法区分「用户改的」和「旧默认值」。
|
|
const std::string template_text = "[voice_input]\nasr_token = \"<YOUR_OWN_ASR_TOKEN>\"\nlanguage = \"zh-cn\"\n";
|
|
const std::string user_text = "[voice_input]\nasr_token = \"sk-real-token\"\nlanguage = \"en\"\n";
|
|
|
|
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()),
|
|
"[voice_input]\nasr_token = \"sk-real-token\"\nlanguage = \"en\"\n");
|
|
}
|
|
|
|
TEST_CASE(config_merge_handles_multiline_string_values)
|
|
{
|
|
const std::string template_text = "[ai_assistant]\n"
|
|
"prompt = \"\"\"new\ndefault prompt\"\"\"\n"
|
|
"model = \"v2\"\n";
|
|
const std::string user_text = "[ai_assistant]\n"
|
|
"prompt = \"\"\"my\nown prompt\"\"\"\n"
|
|
"model = \"v1\"\n";
|
|
const std::string baseline_text = "[ai_assistant]\n"
|
|
"prompt = \"\"\"old\ndefault prompt\"\"\"\n"
|
|
"model = \"v1\"\n";
|
|
|
|
const std::string expected = "[ai_assistant]\n"
|
|
"prompt = \"\"\"my\nown prompt\"\"\"\n"
|
|
"model = \"v2\"\n";
|
|
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, baseline_text), expected);
|
|
}
|
|
|
|
TEST_CASE(config_merge_ignores_assignments_inside_multiline_strings)
|
|
{
|
|
const std::string template_text = "[ai_assistant]\n"
|
|
"prompt = \"\"\"say hi\"\"\"\n"
|
|
"model = \"v2\"\n";
|
|
// prompt 正文里的 model = ... 只是提示词内容,不能被当成一个键。
|
|
const std::string user_text = "[ai_assistant]\n"
|
|
"prompt = \"\"\"say hi\nmodel = \"hijacked\"\n\"\"\"\n";
|
|
|
|
const std::string expected = "[ai_assistant]\n"
|
|
"prompt = \"\"\"say hi\nmodel = \"hijacked\"\n\"\"\"\n"
|
|
"model = \"v2\"\n";
|
|
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()), expected);
|
|
}
|
|
|
|
TEST_CASE(config_merge_ignores_commented_out_keys)
|
|
{
|
|
const std::string template_text = "[input]\n# schema = \"wubi\"\nschema = \"shuangpin\"\n";
|
|
const std::string user_text = "[input]\n# schema = \"quanpin\"\nschema = \"wubi\"\n";
|
|
|
|
REQUIRE_EQ(MergeConfigIntoTemplate(template_text, user_text, std::string()),
|
|
"[input]\n# schema = \"wubi\"\nschema = \"wubi\"\n");
|
|
}
|
|
|
|
TEST_CASE(configured_voice_input_is_handed_out_as_a_snapshot)
|
|
{
|
|
// LoadImeConfig rewrites the whole voice config on the IPC worker thread (asr_tokens / polish_tokens are cleared
|
|
// and refilled) while the voice control thread and the low-level keyboard hook read it on every keystroke. Handing
|
|
// out a reference lets those readers walk strings and maps that are being rewritten, so this accessor must return a
|
|
// snapshot by value.
|
|
REQUIRE(!std::is_reference_v<decltype(GetConfiguredVoiceInput())>);
|
|
REQUIRE((std::is_same_v<decltype(GetConfiguredVoiceInput()), VoiceInputConfig>));
|
|
|
|
const VoiceInputConfig snapshot = GetConfiguredVoiceInput();
|
|
REQUIRE_EQ(snapshot.commit_mode, VoiceInputConfig().commit_mode);
|
|
}
|
|
|
|
// 读取安装器真正分发的模板,防止开发配置齐全、安装模板漏项导致升级后丢失设置。
|
|
TEST_CASE(shipped_translation_settings_survive_template_upgrade)
|
|
{
|
|
std::ifstream input(MSIME_DEFAULT_CONFIG_PATH, std::ios::binary);
|
|
REQUIRE(static_cast<bool>(input));
|
|
const std::string installed((std::istreambuf_iterator<char>(input)), {});
|
|
const std::string configured =
|
|
"[custom_translation]\nenabled = true\nendpoint = \"https://translation.example/translate\"\n"
|
|
"api_key = \"test-translation-key\"\n[tencent_tmt]\ntarget_language = \"ja\"\n";
|
|
const std::string legacy_template = "[tencent_tmt]\nenabled = true\n";
|
|
|
|
// 同时覆盖旧版本缺少这些字段,以及新版本已经包含这些字段的升级。
|
|
for (const auto &baseline : {legacy_template, installed})
|
|
{
|
|
const auto merged = toml::parse(MergeConfigIntoTemplate(installed, configured, baseline));
|
|
REQUIRE(merged["custom_translation"]["enabled"].value_or(false));
|
|
REQUIRE_EQ(merged["custom_translation"]["endpoint"].value_or(std::string()),
|
|
std::string("https://translation.example/translate"));
|
|
REQUIRE_EQ(merged["custom_translation"]["api_key"].value_or(std::string()),
|
|
std::string("test-translation-key"));
|
|
REQUIRE_EQ(merged["tencent_tmt"]["target_language"].value_or(std::string()), std::string("ja"));
|
|
}
|
|
}
|