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