1 /* 2 * PROJECT: ReactOS api tests 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Test for IAutoComplete objects 5 * COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 #define _UNICODE 8 #define UNICODE 9 #include <apitest.h> 10 #include <shlobj.h> 11 #include <atlbase.h> 12 #include <tchar.h> 13 #include <atlcom.h> 14 #include <atlwin.h> 15 #include <atlstr.h> 16 #include <atlsimpcoll.h> 17 #include <shlwapi.h> 18 #include <strsafe.h> 19 20 //#define MANUAL_DEBUGGING 21 22 // compare wide strings 23 #define ok_wstri(x, y) \ 24 ok(lstrcmpiW(x, y) == 0, "Wrong string. Expected '%S', got '%S'\n", y, x) 25 26 struct CCoInit 27 { 28 CCoInit() { hr = CoInitialize(NULL); } 29 ~CCoInit() { if (SUCCEEDED(hr)) { CoUninitialize(); } } 30 HRESULT hr; 31 }; 32 33 // create an EDIT control 34 static HWND MyCreateEditCtrl(INT x, INT y, INT cx, INT cy) 35 { 36 DWORD style = WS_POPUPWINDOW | WS_BORDER; 37 DWORD exstyle = WS_EX_CLIENTEDGE; 38 return CreateWindowExW(exstyle, L"EDIT", NULL, style, x, y, cx, cy, 39 NULL, NULL, GetModuleHandleW(NULL), NULL); 40 } 41 42 static BOOL s_bReset = FALSE; 43 static BOOL s_bExpand = FALSE; 44 static CStringW s_strExpand; 45 46 // CEnumString class for auto-completion test 47 class CEnumString : public IEnumString, public IACList2 48 { 49 public: 50 CEnumString() : m_cRefs(0), m_nIndex(0), m_nCount(0), m_pList(NULL) 51 { 52 trace("CEnumString::CEnumString(%p)\n", this); 53 } 54 55 virtual ~CEnumString() 56 { 57 trace("CEnumString::~CEnumString(%p)\n", this); 58 for (UINT i = 0; i < m_nCount; ++i) 59 { 60 CoTaskMemFree(m_pList[i]); 61 m_pList[i] = NULL; 62 } 63 CoTaskMemFree(m_pList); 64 } 65 66 VOID SetList(UINT nCount, LPWSTR *pList) 67 { 68 m_nCount = nCount; 69 m_pList = pList; 70 } 71 72 STDMETHODIMP QueryInterface(REFIID iid, VOID** ppv) override 73 { 74 if (iid == IID_IUnknown || iid == IID_IEnumString) 75 { 76 trace("IID_IEnumString\n"); 77 AddRef(); 78 *ppv = static_cast<IEnumString *>(this); 79 return S_OK; 80 } 81 if (iid == IID_IACList || iid == IID_IACList2) 82 { 83 trace("IID_IACList2\n"); 84 AddRef(); 85 *ppv = static_cast<IACList2 *>(this); 86 return S_OK; 87 } 88 return E_NOINTERFACE; 89 } 90 STDMETHODIMP_(ULONG) AddRef() override 91 { 92 //trace("CEnumString::AddRef\n"); 93 ++m_cRefs; 94 return m_cRefs; 95 } 96 STDMETHODIMP_(ULONG) Release() override 97 { 98 //trace("CEnumString::Release\n"); 99 --m_cRefs; 100 if (m_cRefs == 0) 101 { 102 delete this; 103 return 0; 104 } 105 return m_cRefs; 106 } 107 108 STDMETHODIMP Next(ULONG celt, LPWSTR* rgelt, ULONG* pceltFetched) override 109 { 110 if (rgelt) 111 *rgelt = NULL; 112 if (pceltFetched) 113 *pceltFetched = 0; 114 if (celt != 1 || !rgelt || !pceltFetched) 115 return E_INVALIDARG; 116 if (m_nIndex >= m_nCount) 117 return S_FALSE; 118 119 SHStrDupW(m_pList[m_nIndex], rgelt); 120 ++m_nIndex; 121 if (!*rgelt) 122 return E_OUTOFMEMORY; 123 *pceltFetched = 1; 124 return S_OK; 125 } 126 STDMETHODIMP Skip(ULONG celt) override 127 { 128 trace("CEnumString::Skip(%lu)\n", celt); 129 return E_NOTIMPL; 130 } 131 STDMETHODIMP Reset() override 132 { 133 trace("CEnumString::Reset\n"); 134 m_nIndex = 0; 135 s_bReset = TRUE; 136 return S_OK; 137 } 138 STDMETHODIMP Clone(IEnumString** ppenum) override 139 { 140 trace("CEnumString::Clone()\n"); 141 return E_NOTIMPL; 142 } 143 144 STDMETHODIMP Expand(PCWSTR pszExpand) override 145 { 146 trace("CEnumString::Expand(%S)\n", pszExpand); 147 s_bExpand = TRUE; 148 s_strExpand = pszExpand; 149 return S_OK; 150 } 151 STDMETHODIMP GetOptions(DWORD *pdwFlag) override 152 { 153 trace("CEnumString::GetOption(%p)\n", pdwFlag); 154 return S_OK; 155 } 156 STDMETHODIMP SetOptions(DWORD dwFlag) override 157 { 158 trace("CEnumString::SetOption(0x%lX)\n", dwFlag); 159 return S_OK; 160 } 161 162 protected: 163 ULONG m_cRefs; 164 UINT m_nIndex, m_nCount; 165 LPWSTR *m_pList; 166 }; 167 168 // range of WCHAR (inclusive) 169 struct RANGE 170 { 171 WCHAR from, to; 172 }; 173 174 //#define OUTPUT_TABLE // generate the table to analyze 175 176 #ifndef OUTPUT_TABLE 177 // comparison of two ranges 178 static int __cdecl RangeCompare(const void *x, const void *y) 179 { 180 const RANGE *a = (const RANGE *)x; 181 const RANGE *b = (const RANGE *)y; 182 if (a->to < b->from) 183 return -1; 184 if (b->to < a->from) 185 return 1; 186 return 0; 187 } 188 189 // is the WCHAR a word break? 190 static __inline BOOL IsWordBreak(WCHAR ch) 191 { 192 static const RANGE s_ranges[] = 193 { 194 { 0x0009, 0x0009 }, { 0x0020, 0x002f }, { 0x003a, 0x0040 }, { 0x005b, 0x0060 }, 195 { 0x007b, 0x007e }, { 0x00ab, 0x00ab }, { 0x00ad, 0x00ad }, { 0x00bb, 0x00bb }, 196 { 0x02c7, 0x02c7 }, { 0x02c9, 0x02c9 }, { 0x055d, 0x055d }, { 0x060c, 0x060c }, 197 { 0x2002, 0x200b }, { 0x2013, 0x2014 }, { 0x2016, 0x2016 }, { 0x2018, 0x2018 }, 198 { 0x201c, 0x201d }, { 0x2022, 0x2022 }, { 0x2025, 0x2027 }, { 0x2039, 0x203a }, 199 { 0x2045, 0x2046 }, { 0x207d, 0x207e }, { 0x208d, 0x208e }, { 0x226a, 0x226b }, 200 { 0x2574, 0x2574 }, { 0x3001, 0x3003 }, { 0x3005, 0x3005 }, { 0x3008, 0x3011 }, 201 { 0x3014, 0x301b }, { 0x301d, 0x301e }, { 0x3041, 0x3041 }, { 0x3043, 0x3043 }, 202 { 0x3045, 0x3045 }, { 0x3047, 0x3047 }, { 0x3049, 0x3049 }, { 0x3063, 0x3063 }, 203 { 0x3083, 0x3083 }, { 0x3085, 0x3085 }, { 0x3087, 0x3087 }, { 0x308e, 0x308e }, 204 { 0x309b, 0x309e }, { 0x30a1, 0x30a1 }, { 0x30a3, 0x30a3 }, { 0x30a5, 0x30a5 }, 205 { 0x30a7, 0x30a7 }, { 0x30a9, 0x30a9 }, { 0x30c3, 0x30c3 }, { 0x30e3, 0x30e3 }, 206 { 0x30e5, 0x30e5 }, { 0x30e7, 0x30e7 }, { 0x30ee, 0x30ee }, { 0x30f5, 0x30f6 }, 207 { 0x30fc, 0x30fe }, { 0xfd3e, 0xfd3f }, { 0xfe30, 0xfe31 }, { 0xfe33, 0xfe44 }, 208 { 0xfe4f, 0xfe51 }, { 0xfe59, 0xfe5e }, { 0xff08, 0xff09 }, { 0xff0c, 0xff0c }, 209 { 0xff0e, 0xff0e }, { 0xff1c, 0xff1c }, { 0xff1e, 0xff1e }, { 0xff3b, 0xff3b }, 210 { 0xff3d, 0xff3d }, { 0xff40, 0xff40 }, { 0xff5b, 0xff5e }, { 0xff61, 0xff64 }, 211 { 0xff67, 0xff70 }, { 0xff9e, 0xff9f }, { 0xffe9, 0xffe9 }, { 0xffeb, 0xffeb }, 212 }; 213 #ifndef NDEBUG 214 // check the table if first time 215 static BOOL s_bFirstTime = TRUE; 216 if (s_bFirstTime) 217 { 218 s_bFirstTime = FALSE; 219 for (UINT i = 0; i < _countof(s_ranges); ++i) 220 { 221 ATLASSERT(s_ranges[i].from <= s_ranges[i].to); 222 } 223 for (UINT i = 0; i + 1 < _countof(s_ranges); ++i) 224 { 225 ATLASSERT(s_ranges[i].to < s_ranges[i + 1].from); 226 } 227 } 228 #endif 229 RANGE range = { ch, ch }; 230 return !!bsearch(&range, s_ranges, _countof(s_ranges), sizeof(RANGE), RangeCompare); 231 } 232 #endif 233 234 static VOID DoWordBreakProc(EDITWORDBREAKPROC fn) 235 { 236 #ifdef OUTPUT_TABLE 237 // generate the table text 238 WORD wType1, wType2, wType3; 239 for (DWORD i = 0; i <= 0xFFFF; ++i) 240 { 241 WCHAR ch = (WCHAR)i; 242 GetStringTypeW(CT_CTYPE1, &ch, 1, &wType1); 243 GetStringTypeW(CT_CTYPE2, &ch, 1, &wType2); 244 GetStringTypeW(CT_CTYPE3, &ch, 1, &wType3); 245 BOOL b = fn(&ch, 0, 1, WB_ISDELIMITER); 246 trace("%u\t0x%04x\t0x%04x\t0x%04x\t0x%04x\n", b, wType1, wType2, wType3, ch); 247 } 248 #else 249 // check the word break procedure 250 for (DWORD i = 0; i <= 0xFFFF; ++i) 251 { 252 WCHAR ch = (WCHAR)i; 253 BOOL b1 = fn(&ch, 0, 1, WB_ISDELIMITER); 254 BOOL b2 = IsWordBreak(ch); 255 ok(b1 == b2, "ch:0x%04x, b1:%d, b2:%d\n", ch, b1, b2); 256 } 257 #endif 258 } 259 260 // the testcase A 261 static VOID 262 DoTestCaseA(INT x, INT y, INT cx, INT cy, LPCWSTR pszInput, 263 LPWSTR *pList, UINT nCount, BOOL bDowner, BOOL bLong) 264 { 265 MSG msg; 266 s_bExpand = s_bReset = FALSE; 267 268 // create EDIT control 269 HWND hwndEdit = MyCreateEditCtrl(x, y, cx, cy); 270 ok(hwndEdit != NULL, "hwndEdit was NULL\n"); 271 ShowWindowAsync(hwndEdit, SW_SHOWNORMAL); 272 273 // get word break procedure 274 EDITWORDBREAKPROC fn1 = 275 (EDITWORDBREAKPROC)SendMessageW(hwndEdit, EM_GETWORDBREAKPROC, 0, 0); 276 ok(fn1 == NULL, "fn1 was %p\n", fn1); 277 278 // set the list data 279 CComPtr<CEnumString> pEnum(new CEnumString()); 280 pEnum->SetList(nCount, pList); 281 282 // create auto-completion object 283 CComPtr<IAutoComplete2> pAC; 284 HRESULT hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, 285 IID_IAutoComplete2, (VOID **)&pAC); 286 ok_hr(hr, S_OK); 287 288 // enable auto-suggest 289 hr = pAC->SetOptions(ACO_AUTOSUGGEST); 290 ok_hr(hr, S_OK); 291 292 // initialize 293 IUnknown *punk = static_cast<IEnumString *>(pEnum); 294 hr = pAC->Init(hwndEdit, punk, NULL, NULL); // IAutoComplete::Init 295 ok_hr(hr, S_OK); 296 297 #ifdef MANUAL_DEBUGGING 298 trace("enter MANUAL_DEBUGGING...\n"); 299 trace("NOTE: You can quit EDIT control by Alt+F4.\n"); 300 while (GetMessageW(&msg, NULL, 0, 0)) 301 { 302 TranslateMessage(&msg); 303 DispatchMessageW(&msg); 304 if (!IsWindow(hwndEdit)) 305 break; 306 } 307 trace("leave MANUAL_DEBUGGING...\n"); 308 return; 309 #endif 310 311 // check expansion 312 ok_int(s_bExpand, FALSE); 313 // check reset 314 ok_int(s_bReset, FALSE); 315 316 // input 317 SetFocus(hwndEdit); 318 WCHAR chLast = 0; 319 for (UINT i = 0; pszInput[i]; ++i) 320 { 321 PostMessageW(hwndEdit, WM_CHAR, pszInput[i], 0); 322 chLast = pszInput[i]; 323 } 324 325 // wait for hwndDropDown 326 DWORD style, exstyle; 327 HWND hwndDropDown; 328 LONG_PTR id; 329 for (INT i = 0; i < 100; ++i) 330 { 331 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 332 { 333 TranslateMessage(&msg); 334 DispatchMessageW(&msg); 335 } 336 hwndDropDown = FindWindowW(L"Auto-Suggest Dropdown", L""); 337 if (IsWindowVisible(hwndDropDown)) 338 break; 339 Sleep(100); 340 } 341 ok(hwndDropDown != NULL, "hwndDropDown was NULL\n"); 342 ok_int(IsWindowVisible(hwndDropDown), TRUE); 343 344 // check word break procedure 345 static BOOL s_bFirstTime = TRUE; 346 if (s_bFirstTime) 347 { 348 s_bFirstTime = FALSE; 349 EDITWORDBREAKPROC fn2 = 350 (EDITWORDBREAKPROC)SendMessageW(hwndEdit, EM_GETWORDBREAKPROC, 0, 0); 351 ok(fn1 != fn2, "fn1 == fn2\n"); 352 ok(fn2 != NULL, "fn2 was NULL\n"); 353 if (fn2) 354 DoWordBreakProc(fn2); 355 else 356 skip("fn2 == NULL\n"); 357 } 358 359 // take care of the message queue 360 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 361 { 362 TranslateMessage(&msg); 363 DispatchMessageW(&msg); 364 } 365 366 // check reset 367 ok_int(s_bReset, TRUE); 368 369 // get sizes and positions 370 RECT rcEdit, rcDropDown; 371 GetWindowRect(hwndEdit, &rcEdit); 372 GetWindowRect(hwndDropDown, &rcDropDown); 373 trace("rcEdit: (%ld, %ld, %ld, %ld)\n", rcEdit.left, rcEdit.top, rcEdit.right, rcEdit.bottom); 374 trace("rcDropDown: (%ld, %ld, %ld, %ld)\n", rcDropDown.left, rcDropDown.top, rcDropDown.right, rcDropDown.bottom); 375 376 // is it "downer"? 377 ok_int(bDowner, rcEdit.top < rcDropDown.top); 378 379 // check window style and id 380 style = (LONG)GetWindowLongPtrW(hwndDropDown, GWL_STYLE); 381 exstyle = (LONG)GetWindowLongPtrW(hwndDropDown, GWL_EXSTYLE); 382 id = GetWindowLongPtrW(hwndDropDown, GWLP_ID); 383 #define DROPDOWN_STYLE (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | \ 384 WS_CLIPCHILDREN | WS_BORDER) // 0x96800000 385 ok(style == DROPDOWN_STYLE, "style was 0x%08lx\n", style); 386 #define DROPDOWN_EX_STYLE (WS_EX_TOOLWINDOW | WS_EX_TOPMOST | \ 387 WS_EX_NOPARENTNOTIFY) // 0x8c 388 ok_long(exstyle, DROPDOWN_EX_STYLE); 389 ok_long((LONG)id, 0); 390 391 // check class style 392 style = (LONG)GetClassLongPtrW(hwndDropDown, GCL_STYLE); 393 #define DROPDOWN_CLASS_STYLE_1 (CS_DROPSHADOW | CS_SAVEBITS) 394 #define DROPDOWN_CLASS_STYLE_2 0 395 ok(style == DROPDOWN_CLASS_STYLE_1 /* Win10 */ || 396 style == DROPDOWN_CLASS_STYLE_2 /* WinXP/Win2k3 */, 397 "style was 0x%08lx\n", style); 398 399 // get client rectangle 400 RECT rcClient; 401 GetClientRect(hwndDropDown, &rcClient); 402 trace("rcClient: (%ld, %ld, %ld, %ld)\n", 403 rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); 404 405 HWND hwndScrollBar, hwndSizeBox, hwndList, hwndNone; 406 WCHAR szClass[64]; 407 408 // scroll bar 409 hwndScrollBar = GetTopWindow(hwndDropDown); 410 ok(hwndScrollBar != NULL, "hwndScrollBar was NULL\n"); 411 GetClassNameW(hwndScrollBar, szClass, _countof(szClass)); 412 ok_wstri(szClass, L"ScrollBar"); 413 style = (LONG)GetWindowLongPtrW(hwndScrollBar, GWL_STYLE); 414 exstyle = (LONG)GetWindowLongPtrW(hwndScrollBar, GWL_EXSTYLE); 415 id = GetWindowLongPtrW(hwndScrollBar, GWLP_ID); 416 #define SCROLLBAR_STYLE_1 (WS_CHILD | WS_VISIBLE | SBS_BOTTOMALIGN | SBS_VERT) // 0x50000005 417 #define SCROLLBAR_STYLE_2 (WS_CHILD | SBS_BOTTOMALIGN | SBS_VERT) // 0x40000005 418 if (bLong) 419 ok(style == SCROLLBAR_STYLE_1, "style was 0x%08lx\n", style); 420 else 421 ok(style == SCROLLBAR_STYLE_2, "style was 0x%08lx\n", style); 422 ok_long(exstyle, 0); 423 ok_long((LONG)id, 0); 424 425 // size-box 426 hwndSizeBox = GetNextWindow(hwndScrollBar, GW_HWNDNEXT); 427 ok(hwndSizeBox != NULL, "hwndSizeBox was NULL\n"); 428 GetClassNameW(hwndSizeBox, szClass, _countof(szClass)); 429 ok_wstri(szClass, L"ScrollBar"); 430 style = (LONG)GetWindowLongPtrW(hwndSizeBox, GWL_STYLE); 431 exstyle = (LONG)GetWindowLongPtrW(hwndSizeBox, GWL_EXSTYLE); 432 id = GetWindowLongPtrW(hwndSizeBox, GWLP_ID); 433 #define SIZEBOX_STYLE_1 \ 434 (WS_CHILD | WS_VISIBLE | SBS_SIZEBOX | SBS_SIZEBOXBOTTOMRIGHTALIGN) // 0x5000000c 435 #define SIZEBOX_STYLE_2 \ 436 (WS_CHILD | WS_VISIBLE | SBS_SIZEBOX) // 0x50000008 437 ok(style == SIZEBOX_STYLE_1 /* Win10 */ || 438 style == SIZEBOX_STYLE_2 /* Win2k3/WinXP */, "style was 0x%08lx\n", style); 439 ok_long(exstyle, 0); 440 ok_long((LONG)id, 0); 441 442 // the list 443 hwndList = GetNextWindow(hwndSizeBox, GW_HWNDNEXT); 444 ok(hwndList != NULL, "hwndList was NULL\n"); 445 GetClassNameW(hwndList, szClass, _countof(szClass)); 446 ok_wstri(szClass, WC_LISTVIEWW); // L"SysListView32" 447 style = (LONG)GetWindowLongPtrW(hwndList, GWL_STYLE); 448 exstyle = (LONG)GetWindowLongPtrW(hwndList, GWL_EXSTYLE); 449 id = GetWindowLongPtrW(hwndList, GWLP_ID); 450 #define LIST_STYLE_1 \ 451 (WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_CLIPSIBLINGS | \ 452 LVS_NOCOLUMNHEADER | LVS_OWNERDATA | LVS_OWNERDRAWFIXED | \ 453 LVS_SINGLESEL | LVS_REPORT) // 0x54205405 454 #define LIST_STYLE_2 \ 455 (WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | LVS_NOCOLUMNHEADER | \ 456 LVS_OWNERDATA | LVS_OWNERDRAWFIXED | LVS_SINGLESEL | LVS_REPORT) // 0x54005405 457 if (bLong) 458 ok(style == LIST_STYLE_1, "style was 0x%08lx\n", style); 459 else 460 ok(style == LIST_STYLE_2, "style was 0x%08lx\n", style); 461 ok_long(exstyle, 0); 462 ok_long((LONG)id, 0); 463 #define LIST_EXTENDED_LV_STYLE_1 \ 464 (LVS_EX_DOUBLEBUFFER | LVS_EX_ONECLICKACTIVATE | \ 465 LVS_EX_FULLROWSELECT | LVS_EX_TRACKSELECT) // 0x10068 466 #define LIST_EXTENDED_LV_STYLE_2 \ 467 (LVS_EX_ONECLICKACTIVATE | LVS_EX_FULLROWSELECT | \ 468 LVS_EX_TRACKSELECT) // 0x68 469 exstyle = ListView_GetExtendedListViewStyle(hwndList); 470 ok(exstyle == LIST_EXTENDED_LV_STYLE_1 /* Win10 */ || 471 exstyle == LIST_EXTENDED_LV_STYLE_2 /* WinXP/Win2k3 */, 472 "exstyle was 0x%08lx\n", exstyle); 473 474 // no more controls 475 hwndNone = GetNextWindow(hwndList, GW_HWNDNEXT); 476 ok(hwndNone == NULL, "hwndNone was %p\n", hwndNone); 477 478 // get rectangles of controls 479 RECT rcScrollBar, rcSizeBox, rcList; 480 GetWindowRect(hwndScrollBar, &rcScrollBar); 481 MapWindowPoints(NULL, hwndDropDown, (LPPOINT)&rcScrollBar, 2); 482 GetWindowRect(hwndSizeBox, &rcSizeBox); 483 MapWindowPoints(NULL, hwndDropDown, (LPPOINT)&rcSizeBox, 2); 484 GetWindowRect(hwndList, &rcList); 485 MapWindowPoints(NULL, hwndDropDown, (LPPOINT)&rcList, 2); 486 trace("rcScrollBar: (%ld, %ld, %ld, %ld)\n", rcScrollBar.left, rcScrollBar.top, 487 rcScrollBar.right, rcScrollBar.bottom); 488 trace("rcSizeBox: (%ld, %ld, %ld, %ld)\n", rcSizeBox.left, rcSizeBox.top, 489 rcSizeBox.right, rcSizeBox.bottom); 490 trace("rcList: (%ld, %ld, %ld, %ld)\n", rcList.left, rcList.top, 491 rcList.right, rcList.bottom); 492 493 // are they visible? 494 ok_int(IsWindowVisible(hwndDropDown), TRUE); 495 ok_int(IsWindowVisible(hwndEdit), TRUE); 496 ok_int(IsWindowVisible(hwndSizeBox), TRUE); 497 ok_int(IsWindowVisible(hwndList), TRUE); 498 499 // check item count 500 INT nListCount = ListView_GetItemCount(hwndList); 501 if (nListCount < 1000) 502 ok_int(nListCount, nCount); 503 else 504 ok_int(nListCount, 1000); 505 506 // check the positions 507 if (bDowner) // downer 508 { 509 ok_int(rcDropDown.left, rcEdit.left); 510 ok_int(rcDropDown.top, rcEdit.bottom); 511 ok_int(rcDropDown.right, rcEdit.right); 512 //ok_int(rcDropDown.bottom, ???); 513 ok_int(rcSizeBox.left, rcClient.right - GetSystemMetrics(SM_CXVSCROLL)); 514 ok_int(rcSizeBox.top, rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL)); 515 ok_int(rcSizeBox.right, rcClient.right); 516 ok_int(rcSizeBox.bottom, rcClient.bottom); 517 ok_int(rcScrollBar.left, rcClient.right - GetSystemMetrics(SM_CXVSCROLL)); 518 ok_int(rcScrollBar.top, 0); 519 ok_int(rcScrollBar.right, rcClient.right); 520 ok_int(rcScrollBar.bottom, rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL)); 521 ok_int(rcList.left, 0); 522 ok_int(rcList.top, 0); 523 //ok_int(rcList.right, 30160 or 30170???); 524 ok_int(rcList.bottom, rcClient.bottom); 525 } 526 else // upper 527 { 528 ok_int(rcDropDown.left, rcEdit.left); 529 //ok_int(rcDropDown.top, ???); 530 ok_int(rcDropDown.right, rcEdit.right); 531 ok_int(rcDropDown.bottom, rcEdit.top); 532 ok_int(rcSizeBox.left, rcClient.right - GetSystemMetrics(SM_CXVSCROLL)); 533 ok_int(rcSizeBox.top, 0); 534 ok_int(rcSizeBox.right, rcClient.right); 535 ok_int(rcSizeBox.bottom, rcClient.top + GetSystemMetrics(SM_CYHSCROLL)); 536 ok_int(rcScrollBar.left, rcClient.right - GetSystemMetrics(SM_CXVSCROLL)); 537 ok_int(rcScrollBar.top, GetSystemMetrics(SM_CYHSCROLL)); 538 ok_int(rcScrollBar.right, rcClient.right); 539 ok_int(rcScrollBar.bottom, rcClient.bottom); 540 ok_int(rcList.left, 0); 541 ok_int(rcList.top, 0); 542 //ok_int(rcList.right, 30160 or 30170???); 543 ok_int(rcList.bottom, rcClient.bottom); 544 } 545 546 // append WM_QUIT message into message queue 547 PostQuitMessage(0); 548 549 // do the messages 550 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 551 { 552 TranslateMessage(&msg); 553 DispatchMessageW(&msg); 554 Sleep(30); // another thread is working... 555 } 556 557 // destroy the EDIT control and drop-down window 558 DestroyWindow(hwndEdit); 559 DestroyWindow(hwndDropDown); 560 561 // do the messages 562 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 563 { 564 TranslateMessage(&msg); 565 DispatchMessageW(&msg); 566 } 567 568 // check the expansion 569 ok_int(s_bExpand, chLast == L'\\'); 570 } 571 572 struct TEST_B_ENTRY 573 { 574 INT m_line; 575 UINT m_uMsg; 576 WPARAM m_wParam; 577 LPARAM m_lParam; 578 LPCWSTR m_text; 579 BOOL m_bVisible; 580 INT m_ich0, m_ich1; 581 INT m_iItem; 582 BOOL m_bReset, m_bExpand; 583 LPCWSTR m_expand; 584 }; 585 586 static BOOL 587 DoesMatch(LPWSTR *pList, UINT nCount, LPCWSTR psz) 588 { 589 INT cch = lstrlenW(psz); 590 if (cch == 0) 591 return FALSE; 592 for (UINT i = 0; i < nCount; ++i) 593 { 594 if (::StrCmpNIW(pList[i], psz, cch) == 0) 595 return TRUE; 596 } 597 return FALSE; 598 } 599 600 // the testcase B 601 static VOID 602 DoTestCaseB(INT x, INT y, INT cx, INT cy, LPWSTR *pList, UINT nCount, 603 const TEST_B_ENTRY *pEntries, UINT cEntries) 604 { 605 MSG msg; 606 s_bExpand = s_bReset = FALSE; 607 608 // create EDIT control 609 HWND hwndEdit = MyCreateEditCtrl(x, y, cx, cy); 610 ok(hwndEdit != NULL, "hwndEdit was NULL\n"); 611 ShowWindowAsync(hwndEdit, SW_SHOWNORMAL); 612 613 // do messages 614 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 615 { 616 TranslateMessage(&msg); 617 DispatchMessageW(&msg); 618 } 619 ok_int(IsWindowVisible(hwndEdit), TRUE); 620 621 // set the list data 622 CComPtr<CEnumString> pEnum(new CEnumString()); 623 pEnum->SetList(nCount, pList); 624 625 // create auto-completion object 626 CComPtr<IAutoComplete2> pAC; 627 HRESULT hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, 628 IID_IAutoComplete2, (VOID **)&pAC); 629 ok_hr(hr, S_OK); 630 631 // enable auto-suggest 632 hr = pAC->SetOptions(ACO_AUTOSUGGEST); 633 ok_hr(hr, S_OK); 634 635 // initialize 636 IUnknown *punk = static_cast<IEnumString *>(pEnum); 637 hr = pAC->Init(hwndEdit, punk, NULL, NULL); // IAutoComplete::Init 638 ok_hr(hr, S_OK); 639 640 HWND hwndDropDown = NULL, hwndScrollBar = NULL, hwndSizeBox = NULL, hwndList = NULL; 641 // input 642 SetFocus(hwndEdit); 643 Sleep(100); 644 645 for (UINT i = 0; i < cEntries; ++i) 646 { 647 const TEST_B_ENTRY& entry = pEntries[i]; 648 s_bReset = s_bExpand = FALSE; 649 s_strExpand.Empty(); 650 651 UINT uMsg = entry.m_uMsg; 652 WPARAM wParam = entry.m_wParam; 653 LPARAM lParam = entry.m_lParam; 654 PostMessageW(hwndEdit, uMsg, wParam, lParam); 655 656 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) 657 { 658 TranslateMessage(&msg); 659 DispatchMessageW(&msg); 660 661 if (msg.message == WM_CHAR || msg.message == WM_KEYDOWN) 662 Sleep(100); // another thread is working 663 } 664 665 if (!IsWindow(hwndDropDown)) 666 { 667 hwndDropDown = FindWindowW(L"Auto-Suggest Dropdown", L""); 668 hwndScrollBar = hwndSizeBox = hwndList = NULL; 669 } 670 if (IsWindowVisible(hwndDropDown)) 671 { 672 hwndScrollBar = GetTopWindow(hwndDropDown); 673 hwndSizeBox = GetNextWindow(hwndScrollBar, GW_HWNDNEXT); 674 hwndList = GetNextWindow(hwndSizeBox, GW_HWNDNEXT); 675 } 676 677 WCHAR szText[64]; 678 GetWindowTextW(hwndEdit, szText, _countof(szText)); 679 CStringW strText = szText; 680 INT ich0, ich1; 681 SendMessageW(hwndEdit, EM_GETSEL, (WPARAM)&ich0, (LPARAM)&ich1); 682 683 BOOL bVisible = IsWindowVisible(hwndDropDown); 684 ok(bVisible == entry.m_bVisible, "Line %d: bVisible was %d\n", entry.m_line, bVisible); 685 INT iItem = -1; 686 if (IsWindowVisible(hwndList)) 687 { 688 iItem = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED); 689 } 690 BOOL bDidMatch = DoesMatch(pList, nCount, strText); 691 ok(bVisible == bDidMatch, "Line %d: bVisible != bDidMatch\n", entry.m_line); 692 693 if (entry.m_text) 694 ok(strText == entry.m_text, "Line %d: szText was %S\n", entry.m_line, (LPCWSTR)strText); 695 else 696 ok(strText == L"", "Line %d: szText was %S\n", entry.m_line, (LPCWSTR)strText); 697 ok(ich0 == entry.m_ich0, "Line %d: ich0 was %d\n", entry.m_line, ich0); 698 ok(ich1 == entry.m_ich1, "Line %d: ich1 was %d\n", entry.m_line, ich1); 699 ok(iItem == entry.m_iItem, "Line %d: iItem was %d\n", entry.m_line, iItem); 700 ok(s_bReset == entry.m_bReset, "Line %d: s_bReset was %d\n", entry.m_line, s_bReset); 701 ok(s_bExpand == entry.m_bExpand, "Line %d: s_bExpand was %d\n", entry.m_line, s_bExpand); 702 if (entry.m_expand) 703 ok(s_strExpand == entry.m_expand, "Line %d: s_strExpand was %S\n", entry.m_line, (LPCWSTR)s_strExpand); 704 else 705 ok(s_strExpand == L"", "Line %d: s_strExpand was %S\n", entry.m_line, (LPCWSTR)s_strExpand); 706 } 707 708 DestroyWindow(hwndEdit); 709 } 710 711 START_TEST(IAutoComplete) 712 { 713 // initialize COM 714 CCoInit init; 715 ok_hr(init.hr, S_OK); 716 if (!SUCCEEDED(init.hr)) 717 { 718 skip("CoInitialize failed\n"); 719 return; 720 } 721 722 // get screen size 723 HMONITOR hMon = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTOPRIMARY); 724 MONITORINFO mi = { sizeof(mi) }; 725 GetMonitorInfoW(hMon, &mi); 726 const RECT& rcWork = mi.rcWork; 727 trace("rcWork: (%ld, %ld, %ld, %ld)\n", 728 rcWork.left, rcWork.top, rcWork.right, rcWork.bottom); 729 trace("SM_CXVSCROLL: %d, SM_CYHSCROLL: %d\n", 730 GetSystemMetrics(SM_CXVSCROLL), GetSystemMetrics(SM_CYHSCROLL)); 731 732 UINT nCount; 733 LPWSTR *pList; 734 WCHAR szText[64]; 735 736 // Test case #1 (A) 737 trace("Testcase #1 (downer, short) ------------------------------\n"); 738 nCount = 3; 739 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 740 SHStrDupW(L"test\\AA", &pList[0]); 741 SHStrDupW(L"test\\BBB", &pList[1]); 742 SHStrDupW(L"test\\CCC", &pList[2]); 743 DoTestCaseA(0, 0, 100, 30, L"test\\", pList, nCount, TRUE, FALSE); 744 745 // Test case #2 (A) 746 trace("Testcase #2 (downer, long) ------------------------------\n"); 747 nCount = 300; 748 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 749 for (UINT i = 0; i < nCount; ++i) 750 { 751 StringCbPrintfW(szText, sizeof(szText), L"test\\%u", i); 752 SHStrDupW(szText, &pList[i]); 753 } 754 DoTestCaseA(100, 20, 100, 30, L"test\\", pList, nCount, TRUE, TRUE); 755 756 // Test case #3 (A) 757 trace("Testcase #3 (upper, short) ------------------------------\n"); 758 nCount = 2; 759 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 760 SHStrDupW(L"test/AA", &pList[0]); 761 SHStrDupW(L"test/BBB", &pList[0]); 762 SHStrDupW(L"test/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", &pList[1]); 763 DoTestCaseA(rcWork.right - 100, rcWork.bottom - 30, 80, 40, L"test/", 764 pList, nCount, FALSE, FALSE); 765 766 // Test case #4 (A) 767 trace("Testcase #4 (upper, short) ------------------------------\n"); 768 nCount = 2; 769 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 770 SHStrDupW(L"testtest\\AA", &pList[0]); 771 SHStrDupW(L"testtest\\BBB", &pList[0]); 772 SHStrDupW(L"testtest\\CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", &pList[1]); 773 DoTestCaseA(rcWork.right - 100, rcWork.bottom - 30, 80, 40, L"testtest\\", 774 pList, nCount, FALSE, FALSE); 775 776 #ifdef MANUAL_DEBUGGING 777 return; 778 #endif 779 780 // Test case #5 (A) 781 trace("Testcase #5 (upper, long) ------------------------------\n"); 782 nCount = 300; 783 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 784 for (UINT i = 0; i < nCount; ++i) 785 { 786 StringCbPrintfW(szText, sizeof(szText), L"testtest/%u", i); 787 SHStrDupW(szText, &pList[i]); 788 } 789 DoTestCaseA(0, rcWork.bottom - 30, 80, 30, L"testtest/", pList, nCount, FALSE, TRUE); 790 791 // Test case #6 (A) 792 trace("Testcase #6 (upper, long) ------------------------------\n"); 793 nCount = 2000; 794 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 795 for (UINT i = 0; i < nCount; ++i) 796 { 797 StringCbPrintfW(szText, sizeof(szText), L"testtest\\item-%u", i); 798 SHStrDupW(szText, &pList[i]); 799 } 800 DoTestCaseA(0, rcWork.bottom - 30, 80, 40, L"testtest\\", pList, nCount, FALSE, TRUE); 801 802 // Test case #7 (B) 803 trace("Testcase #7 ------------------------------\n"); 804 nCount = 16; 805 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 806 for (UINT i = 0; i < nCount; ++i) 807 { 808 StringCbPrintfW(szText, sizeof(szText), L"test\\item-%u", i); 809 SHStrDupW(szText, &pList[i]); 810 } 811 static const TEST_B_ENTRY testcase7_entries[] = 812 { 813 { __LINE__, WM_CHAR, L't', 0, L"t", TRUE, 1, 1, -1, TRUE }, 814 { __LINE__, WM_CHAR, L'e', 0, L"te", TRUE, 2, 2, -1 }, 815 { __LINE__, WM_CHAR, L's', 0, L"tes", TRUE, 3, 3, -1 }, 816 { __LINE__, WM_CHAR, L'T', 0, L"tesT", TRUE, 4, 4, -1 }, 817 { __LINE__, WM_CHAR, L'\\', 0, L"tesT\\", TRUE, 5, 5, -1, TRUE, TRUE, L"tesT\\" }, 818 { __LINE__, WM_CHAR, L't', 0, L"tesT\\t", FALSE, 6, 6, -1 }, 819 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"tesT\\", TRUE, 5, 5, -1 }, 820 { __LINE__, WM_CHAR, L'i', 0, L"tesT\\i", TRUE, 6, 6, -1 }, 821 { __LINE__, WM_KEYDOWN, VK_DOWN, 0, L"test\\item-0", TRUE, 11, 11, 0 }, 822 { __LINE__, WM_KEYDOWN, VK_DOWN, 0, L"test\\item-1", TRUE, 11, 11, 1 }, 823 { __LINE__, WM_KEYDOWN, VK_UP, 0, L"test\\item-0", TRUE, 11, 11, 0 }, 824 { __LINE__, WM_KEYDOWN, VK_UP, 0, L"tesT\\i", TRUE, 6, 6, -1 }, 825 { __LINE__, WM_KEYDOWN, VK_UP, 0, L"test\\item-9", TRUE, 11, 11, 15 }, 826 { __LINE__, WM_KEYDOWN, VK_DOWN, 0, L"tesT\\i", TRUE, 6, 6, -1 }, 827 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"tesT\\", TRUE, 5, 5, -1 }, 828 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"tesT", TRUE, 4, 4, -1, TRUE }, 829 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"tes", TRUE, 3, 3, -1 }, 830 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"te", TRUE, 2, 2, -1 }, 831 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"t", TRUE, 1, 1, -1 }, 832 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"", FALSE, 0, 0, -1 }, 833 { __LINE__, WM_CHAR, L't', 0, L"t", TRUE, 1, 1, -1, TRUE }, 834 { __LINE__, WM_KEYDOWN, VK_LEFT, 0, L"t", TRUE, 0, 0, -1 }, 835 { __LINE__, WM_KEYDOWN, VK_RIGHT, 0, L"t", TRUE, 1, 1, -1 }, 836 }; 837 DoTestCaseB(0, 0, 100, 30, pList, nCount, testcase7_entries, _countof(testcase7_entries)); 838 839 // Test case #8 (B) 840 trace("Testcase #8 ------------------------------\n"); 841 nCount = 10; 842 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 843 for (UINT i = 0; i < nCount; ++i) 844 { 845 StringCbPrintfW(szText, sizeof(szText), L"test\\item-%u", i); 846 SHStrDupW(szText, &pList[i]); 847 } 848 static const TEST_B_ENTRY testcase8_entries[] = 849 { 850 { __LINE__, WM_CHAR, L'a', 0, L"a", FALSE, 1, 1, -1, TRUE }, 851 { __LINE__, WM_CHAR, L'b', 0, L"ab", FALSE, 2, 2, -1 }, 852 { __LINE__, WM_KEYDOWN, VK_LEFT, 0, L"ab", FALSE, 1, 1, -1 }, 853 { __LINE__, WM_KEYDOWN, VK_LEFT, 0, L"ab", FALSE, 0, 0, -1 }, 854 { __LINE__, WM_KEYDOWN, VK_DELETE, 0, L"b", FALSE, 0, 0, -1, TRUE }, 855 { __LINE__, WM_CHAR, L't', 0, L"tb", FALSE, 1, 1, -1, TRUE }, 856 { __LINE__, WM_CHAR, L'e', 0, L"teb", FALSE, 2, 2, -1 }, 857 { __LINE__, WM_CHAR, L's', 0, L"tesb", FALSE, 3, 3, -1 }, 858 { __LINE__, WM_CHAR, L't', 0, L"testb", FALSE, 4, 4, -1 }, 859 { __LINE__, WM_KEYDOWN, VK_DELETE, 0, L"test", TRUE, 4, 4, -1 }, 860 { __LINE__, WM_CHAR, L'\\', 0, L"test\\", TRUE, 5, 5, -1, TRUE, TRUE, L"test\\" }, 861 { __LINE__, WM_CHAR, L'i', 0, L"test\\i", TRUE, 6, 6, -1 }, 862 { __LINE__, WM_KEYDOWN, VK_DELETE, 0, L"test\\i", TRUE, 6, 6, -1 }, 863 { __LINE__, WM_CHAR, L'z', 0, L"test\\iz", FALSE, 7, 7, -1 }, 864 { __LINE__, WM_KEYDOWN, VK_LEFT, 0, L"test\\iz", FALSE, 6, 6, -1 }, 865 { __LINE__, WM_KEYDOWN, VK_LEFT, 0, L"test\\iz", FALSE, 5, 5, -1 }, 866 { __LINE__, WM_KEYDOWN, VK_DELETE, 0, L"test\\z", FALSE, 5, 5, -1 }, 867 { __LINE__, WM_KEYDOWN, VK_DELETE, 0, L"test\\", TRUE, 5, 5, -1 }, 868 { __LINE__, WM_KEYDOWN, VK_LEFT, 0, L"test\\", TRUE, 4, 4, -1 }, 869 { __LINE__, WM_KEYDOWN, VK_LEFT, 0, L"test\\", TRUE, 3, 3, -1 }, 870 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"tet\\", FALSE, 2, 2, -1, TRUE, TRUE, L"tet\\" }, 871 { __LINE__, WM_KEYDOWN, VK_DELETE, 0, L"te\\", FALSE, 2, 2, -1, TRUE, TRUE, L"te\\" }, 872 { __LINE__, WM_KEYDOWN, VK_DELETE, 0, L"te", TRUE, 2, 2, -1, TRUE }, 873 }; 874 DoTestCaseB(rcWork.right - 100, rcWork.bottom - 30, 80, 40, pList, nCount, testcase8_entries, _countof(testcase8_entries)); 875 876 // Test case #9 (B) 877 trace("Testcase #9 ------------------------------\n"); 878 nCount = 32; 879 pList = (LPWSTR *)CoTaskMemAlloc(nCount * sizeof(LPWSTR)); 880 for (UINT i = 0; i < nCount; ++i) 881 { 882 StringCbPrintfW(szText, sizeof(szText), L"a%u\\b%u\\c%u", i % 2, (i / 2) % 2, i % 4); 883 SHStrDupW(szText, &pList[i]); 884 } 885 static const TEST_B_ENTRY testcase9_entries[] = 886 { 887 { __LINE__, WM_CHAR, L'a', 0, L"a", TRUE, 1, 1, -1, TRUE }, 888 { __LINE__, WM_CHAR, L'0', 0, L"a0", TRUE, 2, 2, -1 }, 889 { __LINE__, WM_CHAR, L'\\', 0, L"a0\\", TRUE, 3, 3, -1, TRUE, TRUE, L"a0\\" }, 890 { __LINE__, WM_CHAR, L'b', 0, L"a0\\b", TRUE, 4, 4, -1 }, 891 { __LINE__, WM_CHAR, L'1', 0, L"a0\\b1", TRUE, 5, 5, -1 }, 892 { __LINE__, WM_CHAR, L'\\', 0, L"a0\\b1\\", TRUE, 6, 6, -1, TRUE, TRUE, L"a0\\b1\\" }, 893 { __LINE__, WM_CHAR, L'c', 0, L"a0\\b1\\c", TRUE, 7, 7, -1 }, 894 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"a0\\b1\\", TRUE, 6, 6, -1 }, 895 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"a0\\b1", TRUE, 5, 5, -1, TRUE, TRUE, L"a0\\" }, 896 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"a0\\b", TRUE, 4, 4, -1 }, 897 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"a0\\", TRUE, 3, 3, -1 }, 898 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"a0", TRUE, 2, 2, -1, TRUE }, 899 { __LINE__, WM_KEYDOWN, VK_BACK, 0, L"a", TRUE, 1, 1, -1 }, 900 { __LINE__, WM_KEYDOWN, VK_DOWN, 0, L"a0\\b0\\c0", TRUE, 8, 8, 0 }, 901 { __LINE__, WM_KEYDOWN, VK_UP, 0, L"a", TRUE, 1, 1, -1 }, 902 }; 903 DoTestCaseB(0, 0, 100, 30, pList, nCount, testcase9_entries, _countof(testcase9_entries)); 904 } 905