From fb58782012d5d0b3ba95b388731fa43a262cfea4 Mon Sep 17 00:00:00 2001 From: Katayama Hirofumi MZ Date: Sat, 29 Oct 2022 13:49:28 +0900 Subject: [PATCH] [IMM32_APITEST] Add JapanImeConvTestA/W testcases (#4820) Add IME conversion tests to verify IMM/IME support. CORE-11700 --- .../rostests/apitests/imm32/CMakeLists.txt | 5 +- .../apitests/imm32/JapanImeConvTest.h | 284 ++++++++++++++++++ .../apitests/imm32/JapanImeConvTestA.c | 9 + .../apitests/imm32/JapanImeConvTestW.c | 9 + modules/rostests/apitests/imm32/resource.rc | 16 + modules/rostests/apitests/imm32/testlist.c | 4 + 6 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 modules/rostests/apitests/imm32/JapanImeConvTest.h create mode 100644 modules/rostests/apitests/imm32/JapanImeConvTestA.c create mode 100644 modules/rostests/apitests/imm32/JapanImeConvTestW.c create mode 100644 modules/rostests/apitests/imm32/resource.rc diff --git a/modules/rostests/apitests/imm32/CMakeLists.txt b/modules/rostests/apitests/imm32/CMakeLists.txt index 30b7dd66a8e..9c6486a8508 100644 --- a/modules/rostests/apitests/imm32/CMakeLists.txt +++ b/modules/rostests/apitests/imm32/CMakeLists.txt @@ -7,7 +7,10 @@ list(APPEND SOURCE imcc.c ImmGetImeInfoEx.c ImmIsUIMessage.c - testlist.c) + JapanImeConvTestA.c + JapanImeConvTestW.c + testlist.c + resource.rc) add_executable(imm32_apitest ${SOURCE}) target_link_libraries(imm32_apitest wine ${PSEH_LIB}) diff --git a/modules/rostests/apitests/imm32/JapanImeConvTest.h b/modules/rostests/apitests/imm32/JapanImeConvTest.h new file mode 100644 index 00000000000..8878553d1c1 --- /dev/null +++ b/modules/rostests/apitests/imm32/JapanImeConvTest.h @@ -0,0 +1,284 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Test for Japanese IME conversion + * COPYRIGHT: Copyright 2022 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ + +#include +#include +#include +#include + +/* + * We emulate some keyboard typing on dialog box and watch the conversion of Japanese IME. + * This program needs Japanese environment and Japanese IME. + * Tested on Japanese WinXP and Japanese Win10. + */ + +#define INTERVAL 300 +#define WM_PRESS_KEY_COMPLETE (WM_USER + 100) + +/* The test entry structure */ +typedef struct tagTEST_ENTRY +{ + const UINT *pKeys; + UINT cKeys; + const void *pvResult; + INT cWM_IME_ENDCOMPOSITION; +} TEST_ENTRY, *PTEST_ENTRY; + +// The Japanese word "テスト" conversion in Romaji +static const UINT s_keys1[] = +{ + 'T', 'E', 'S', 'U', 'T', 'O', VK_SPACE, VK_RETURN +}; +// The Japanese word "調査員" conversion in Romaji +static const UINT s_keys2[] = +{ + 'C', 'H', 'O', 'U', 'S', 'A', 'I', 'N', 'N', VK_SPACE, VK_RETURN +}; + +#ifdef UNICODE + #define AorW(a, w) w +#else + #define AorW(a, w) a +#endif + +/* The test entries */ +static const TEST_ENTRY s_entries[] = +{ + // "テスト" + { s_keys1, _countof(s_keys1), AorW("\x83\x65\x83\x58\x83\x67", L"\x30C6\x30B9\x30C8"), 1 }, + // "調査員" + { s_keys2, _countof(s_keys2), AorW("\x92\xB2\x8D\xB8\x88\xF5", L"\x8ABF\x67FB\x54E1"), 1 }, +}; + +static INT s_iEntry = 0; +static INT s_cWM_IME_ENDCOMPOSITION = 0; +static WNDPROC s_fnOldEditWndProc = NULL; + +#ifdef UNICODE +static LPSTR WideToAnsi(INT nCodePage, LPCWSTR pszWide) +{ + static CHAR s_sz[512]; + WideCharToMultiByte(nCodePage, 0, pszWide, -1, s_sz, _countof(s_sz), NULL, NULL); + return s_sz; +} +#endif + +/* The window procedure for textbox to watch the IME conversion */ +static LRESULT CALLBACK +EditWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_IME_ENDCOMPOSITION: + { + const TEST_ENTRY *entry = &s_entries[s_iEntry]; + HIMC hIMC; + LONG cbResult, cbBuffer; + LPTSTR pszResult; + + /* Check conversion results of composition string */ + hIMC = ImmGetContext(hwnd); + cbResult = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); + trace("cbResult: %ld\n", cbResult); + if (cbResult > 0) /* Ignore zero string */ + { + ok(hIMC != NULL, "hIMC was NULL\n"); + ++s_cWM_IME_ENDCOMPOSITION; + + cbBuffer = cbResult + sizeof(WCHAR); + pszResult = (LPTSTR)calloc(cbBuffer, sizeof(BYTE)); /* Zero-fill */ + ok(pszResult != NULL, "pszResult was NULL\n"); + ImmGetCompositionString(hIMC, GCS_RESULTSTR, pszResult, cbBuffer); +#ifdef UNICODE + trace("%s\n", WideToAnsi(CP_ACP, (LPTSTR)pszResult)); +#else + trace("%s\n", (LPTSTR)pszResult); +#endif + ok(lstrcmp(pszResult, (LPTSTR)entry->pvResult) == 0, "pszResult differs\n"); + free(pszResult); + } + + ImmReleaseContext(hwnd, hIMC); + } + break; + } + + return CallWindowProc(s_fnOldEditWndProc, hwnd, uMsg, wParam, lParam); +} + +/* Timer IDs */ +#define STAGE_1 10001 +#define STAGE_2 10002 +#define STAGE_3 10003 +#define STAGE_4 10004 +#define STAGE_5 10005 + +/* WM_INITDIALOG */ +static BOOL OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) +{ + /* Subclass the textbox to watch the IME conversion */ + HWND hEdt1 = GetDlgItem(hwnd, edt1); + s_fnOldEditWndProc = (WNDPROC)SetWindowLongPtr(hEdt1, GWLP_WNDPROC, (LONG_PTR)EditWindowProc); + + /* Go to first stage */ + SetTimer(hwnd, STAGE_1, INTERVAL, 0); + return TRUE; +} + +/* WM_COMMAND */ +static void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) +{ + switch (id) + { + case IDOK: + case IDCANCEL: + EndDialog(hwnd, id); + break; + } +} + +/* Emulate keyboard typing */ +static VOID PressKey(UINT vk) +{ + INPUT inputs[2]; + ZeroMemory(inputs, sizeof(inputs)); + inputs[0].type = INPUT_KEYBOARD; + inputs[0].ki.wVk = vk; + inputs[1].type = INPUT_KEYBOARD; + inputs[1].ki.wVk = vk; + inputs[1].ki.dwFlags = KEYEVENTF_KEYUP; + SendInput(_countof(inputs), inputs, sizeof(INPUT)); +} + +/* WM_TIMER */ +static void OnTimer(HWND hwnd, UINT id) +{ + HIMC hIMC; + INT i; + const TEST_ENTRY *entry = &s_entries[s_iEntry]; + static DWORD dwOldConversion, dwOldSentence; + + KillTimer(hwnd, id); + + switch (id) + { + case STAGE_1: + /* Check focus. See WM_INITDIALOG return code. */ + ok(GetFocus() == GetDlgItem(hwnd, edt1), "GetFocus() was %p\n", GetFocus()); + + hIMC = ImmGetContext(hwnd); + ok(hIMC != NULL, "hIMC was NULL"); + if (hIMC) + { + /* Open the IME */ + ImmSetOpenStatus(hIMC, TRUE); + /* Save the IME conversion status */ + ImmGetConversionStatus(hIMC, &dwOldConversion, &dwOldSentence); + /* Modify the IME conversion status */ + ImmSetConversionStatus(hIMC, + IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN | IME_CMODE_NATIVE, + IME_SMODE_SINGLECONVERT); + + ImmReleaseContext(hwnd, hIMC); + } + /* Initialize the counter */ + s_cWM_IME_ENDCOMPOSITION = 0; + /* Go to next stage */ + SetTimer(hwnd, STAGE_2, INTERVAL, NULL); + break; + + case STAGE_2: + /* Emulate keyboard typing */ + for (i = 0; i < entry->cKeys; ++i) + { + PressKey(entry->pKeys[i]); + } + /* Wait for message queue processed */ + PostMessage(hwnd, WM_PRESS_KEY_COMPLETE, 0, 0); + break; + + case STAGE_3: + /* Revert the IME conversion status */ + hIMC = ImmGetContext(hwnd); + ok(hIMC != NULL, "hIMC was NULL"); + if (hIMC) + { + ImmSetConversionStatus(hIMC, dwOldConversion, dwOldSentence); + ImmReleaseContext(hwnd, hIMC); + } + /* Go to next stage */ + SetTimer(hwnd, STAGE_4, INTERVAL, NULL); + break; + + case STAGE_4: + /* Check the counter */ + ok_int(s_cWM_IME_ENDCOMPOSITION, entry->cWM_IME_ENDCOMPOSITION); + if (s_cWM_IME_ENDCOMPOSITION < entry->cWM_IME_ENDCOMPOSITION) + { + skip("Some tests were skipped.\n"); + } + + /* Go to next test entry */ + ++s_iEntry; + if (s_iEntry == _countof(s_entries)) + PostMessage(hwnd, WM_CLOSE, 0, 0); /* No more entry */ + else + SetTimer(hwnd, STAGE_1, INTERVAL, NULL); + break; + } +} + +/* Dialog procedure */ +static INT_PTR CALLBACK +DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + HANDLE_MSG(hwnd, WM_INITDIALOG, OnInitDialog); + HANDLE_MSG(hwnd, WM_COMMAND, OnCommand); + HANDLE_MSG(hwnd, WM_TIMER, OnTimer); + + case WM_PRESS_KEY_COMPLETE: + /* Message queue is processed. Go to next stage. */ + SetTimer(hwnd, STAGE_3, INTERVAL, NULL); + break; + } + return 0; +} + +#ifdef UNICODE +START_TEST(JapanImeConvTestW) +#else +START_TEST(JapanImeConvTestA) +#endif +{ + /* Is the system Japanese? */ + if (PRIMARYLANGID(GetSystemDefaultLangID()) != LANG_JAPANESE) + { + skip("This testcase is for Japanese only.\n"); + return; + } + + /* Is IMM enabled? */ + if (!GetSystemMetrics(SM_IMMENABLED)) + { + skip("SM_IMMENABLED is OFF.\n"); + return; + } + + /* Check the current keyboard layout is IME */ + if (!ImmIsIME(GetKeyboardLayout(0))) + { + skip("The IME keyboard layout was not default\n"); + return; + } + + DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(1), NULL, DialogProc); + + if (s_iEntry < _countof(s_entries)) + skip("Some tests were skipped.\n"); +} diff --git a/modules/rostests/apitests/imm32/JapanImeConvTestA.c b/modules/rostests/apitests/imm32/JapanImeConvTestA.c new file mode 100644 index 00000000000..72d2e83581f --- /dev/null +++ b/modules/rostests/apitests/imm32/JapanImeConvTestA.c @@ -0,0 +1,9 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Test for Japanese IME conversion + * COPYRIGHT: Copyright 2022 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ + +#undef UNICODE /* ANSI */ +#include "JapanImeConvTest.h" diff --git a/modules/rostests/apitests/imm32/JapanImeConvTestW.c b/modules/rostests/apitests/imm32/JapanImeConvTestW.c new file mode 100644 index 00000000000..9499b861931 --- /dev/null +++ b/modules/rostests/apitests/imm32/JapanImeConvTestW.c @@ -0,0 +1,9 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Test for Japanese IME conversion + * COPYRIGHT: Copyright 2022 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ + +#define UNICODE /* Unicode */ +#include "JapanImeConvTest.h" diff --git a/modules/rostests/apitests/imm32/resource.rc b/modules/rostests/apitests/imm32/resource.rc new file mode 100644 index 00000000000..c01bc75b2da --- /dev/null +++ b/modules/rostests/apitests/imm32/resource.rc @@ -0,0 +1,16 @@ +#include +#include +#pragma code_page(65001) /* UTF-8 */ + +LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT + +1 DIALOG 0, 0, 215, 135 +CAPTION "JapanImeConvTest" +STYLE DS_CENTER | DS_MODALFRAME | WS_POPUPWINDOW | WS_CAPTION +FONT 9, "MS UI Gothic" +{ + EDITTEXT edt1, 8, 7, 147, 14 + LISTBOX lst1, 7, 30, 202, 82, LBS_NOINTEGRALHEIGHT | LBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + DEFPUSHBUTTON "OK", IDOK, 35, 115, 60, 14 + PUSHBUTTON "Cancel", IDCANCEL, 115, 115, 60, 14 +} diff --git a/modules/rostests/apitests/imm32/testlist.c b/modules/rostests/apitests/imm32/testlist.c index d73e9878113..b9c16be52cf 100644 --- a/modules/rostests/apitests/imm32/testlist.c +++ b/modules/rostests/apitests/imm32/testlist.c @@ -7,6 +7,8 @@ extern void func_himc(void); extern void func_imcc(void); extern void func_ImmGetImeInfoEx(void); extern void func_ImmIsUIMessage(void); +extern void func_JapanImeConvTestA(void); +extern void func_JapanImeConvTestW(void); const struct test winetest_testlist[] = { @@ -15,5 +17,7 @@ const struct test winetest_testlist[] = { "imcc", func_imcc }, { "ImmGetImeInfoEx", func_ImmGetImeInfoEx }, { "ImmIsUIMessage", func_ImmIsUIMessage }, + { "JapanImeConvTestA", func_JapanImeConvTestA }, + { "JapanImeConvTestW", func_JapanImeConvTestW }, { 0, 0 } };