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