1 /*
2  * Tests for autocomplete
3  *
4  * Copyright 2008 Jan de Mooij
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #define COBJMACROS
22 
23 #include <stdarg.h>
24 
25 #include "windows.h"
26 #include "shobjidl.h"
27 #include "shlguid.h"
28 #include "initguid.h"
29 #include "shldisp.h"
30 
31 #include "wine/heap.h"
32 #include "wine/test.h"
33 
34 static HWND hMainWnd, hEdit;
35 static HINSTANCE hinst;
36 static int killfocus_count;
37 
38 static void test_invalid_init(void)
39 {
40     HRESULT hr;
41     IAutoComplete *ac;
42     IUnknown *acSource;
43     HWND edit_control;
44 
45     /* AutoComplete instance */
46     hr = CoCreateInstance(&CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,
47                          &IID_IAutoComplete, (void **)&ac);
48     if (hr == REGDB_E_CLASSNOTREG)
49     {
50         win_skip("CLSID_AutoComplete is not registered\n");
51         return;
52     }
53     ok(hr == S_OK, "no IID_IAutoComplete (0x%08x)\n", hr);
54 
55     /* AutoComplete source */
56     hr = CoCreateInstance(&CLSID_ACLMulti, NULL, CLSCTX_INPROC_SERVER,
57                         &IID_IACList, (void **)&acSource);
58     if (hr == REGDB_E_CLASSNOTREG)
59     {
60         win_skip("CLSID_ACLMulti is not registered\n");
61         IAutoComplete_Release(ac);
62         return;
63     }
64     ok(hr == S_OK, "no IID_IACList (0x%08x)\n", hr);
65 
66     edit_control = CreateWindowExA(0, "EDIT", "Some text", 0, 10, 10, 300, 300,
67                        hMainWnd, NULL, hinst, NULL);
68     ok(edit_control != NULL, "Can't create edit control\n");
69 
70     /* The refcount of acSource would be incremented on older Windows. */
71     hr = IAutoComplete_Init(ac, NULL, acSource, NULL, NULL);
72     ok(hr == E_INVALIDARG ||
73        broken(hr == S_OK), /* Win2k/XP/Win2k3 */
74        "Init returned 0x%08x\n", hr);
75     if (hr == E_INVALIDARG)
76     {
77         LONG ref;
78 
79         IUnknown_AddRef(acSource);
80         ref = IUnknown_Release(acSource);
81         ok(ref == 1, "Expected AutoComplete source refcount to be 1, got %d\n", ref);
82     }
83 
84 if (0)
85 {
86     /* Older Windows versions never check the window handle, while newer
87      * versions only check for NULL. Subsequent attempts to initialize the
88      * object after this call succeeds would fail, because initialization
89      * state is determined by whether a non-NULL window handle is stored. */
90     hr = IAutoComplete_Init(ac, (HWND)0xdeadbeef, acSource, NULL, NULL);
91     ok(hr == S_OK, "Init returned 0x%08x\n", hr);
92 
93     /* Tests crash on older Windows. */
94     hr = IAutoComplete_Init(ac, NULL, NULL, NULL, NULL);
95     ok(hr == E_INVALIDARG, "Init returned 0x%08x\n", hr);
96 
97     hr = IAutoComplete_Init(ac, edit_control, NULL, NULL, NULL);
98     ok(hr == E_INVALIDARG, "Init returned 0x%08x\n", hr);
99 }
100 
101     /* bind to edit control */
102     hr = IAutoComplete_Init(ac, edit_control, acSource, NULL, NULL);
103     ok(hr == S_OK, "Init returned 0x%08x\n", hr);
104 
105     /* try invalid parameters after successful initialization .*/
106     hr = IAutoComplete_Init(ac, NULL, NULL, NULL, NULL);
107     ok(hr == E_INVALIDARG ||
108        hr == E_FAIL, /* Win2k/XP/Win2k3 */
109        "Init returned 0x%08x\n", hr);
110 
111     hr = IAutoComplete_Init(ac, NULL, acSource, NULL, NULL);
112     ok(hr == E_INVALIDARG ||
113        hr == E_FAIL, /* Win2k/XP/Win2k3 */
114        "Init returned 0x%08x\n", hr);
115 
116     hr = IAutoComplete_Init(ac, edit_control, NULL, NULL, NULL);
117     ok(hr == E_INVALIDARG ||
118        hr == E_FAIL, /* Win2k/XP/Win2k3 */
119        "Init returned 0x%08x\n", hr);
120 
121     /* try initializing twice on the same control */
122     hr = IAutoComplete_Init(ac, edit_control, acSource, NULL, NULL);
123     ok(hr == E_FAIL, "Init returned 0x%08x\n", hr);
124 
125     /* try initializing with a different control */
126     hr = IAutoComplete_Init(ac, hEdit, acSource, NULL, NULL);
127     ok(hr == E_FAIL, "Init returned 0x%08x\n", hr);
128 
129     DestroyWindow(edit_control);
130 
131     /* try initializing with a different control after
132      * destroying the original initialization control */
133     hr = IAutoComplete_Init(ac, hEdit, acSource, NULL, NULL);
134     ok(hr == E_UNEXPECTED ||
135        hr == E_FAIL, /* Win2k/XP/Win2k3 */
136        "Init returned 0x%08x\n", hr);
137 
138     IUnknown_Release(acSource);
139     IAutoComplete_Release(ac);
140 }
141 static IAutoComplete *test_init(void)
142 {
143     HRESULT r;
144     IAutoComplete *ac;
145     IUnknown *acSource;
146     LONG_PTR user_data;
147 
148     /* AutoComplete instance */
149     r = CoCreateInstance(&CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,
150                          &IID_IAutoComplete, (LPVOID*)&ac);
151     if (r == REGDB_E_CLASSNOTREG)
152     {
153         win_skip("CLSID_AutoComplete is not registered\n");
154         return NULL;
155     }
156     ok(r == S_OK, "no IID_IAutoComplete (0x%08x)\n", r);
157 
158     /* AutoComplete source */
159     r = CoCreateInstance(&CLSID_ACLMulti, NULL, CLSCTX_INPROC_SERVER,
160                         &IID_IACList, (LPVOID*)&acSource);
161     if (r == REGDB_E_CLASSNOTREG)
162     {
163         win_skip("CLSID_ACLMulti is not registered\n");
164         IAutoComplete_Release(ac);
165         return NULL;
166     }
167     ok(r == S_OK, "no IID_IACList (0x%08x)\n", r);
168 
169     user_data = GetWindowLongPtrA(hEdit, GWLP_USERDATA);
170     ok(user_data == 0, "Expected the edit control user data to be zero\n");
171 
172     /* bind to edit control */
173     r = IAutoComplete_Init(ac, hEdit, acSource, NULL, NULL);
174     ok(r == S_OK, "Init returned 0x%08x\n", r);
175 
176     user_data = GetWindowLongPtrA(hEdit, GWLP_USERDATA);
177     ok(user_data == 0, "Expected the edit control user data to be zero\n");
178 
179     IUnknown_Release(acSource);
180 
181     return ac;
182 }
183 
184 static void test_killfocus(void)
185 {
186     /* Test if WM_KILLFOCUS messages are handled properly by checking if
187      * the parent receives an EN_KILLFOCUS message. */
188     SetFocus(hEdit);
189     killfocus_count = 0;
190     SetFocus(0);
191     ok(killfocus_count == 1, "Expected one EN_KILLFOCUS message, got: %d\n", killfocus_count);
192 }
193 
194 static LRESULT CALLBACK MyWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
195 {
196     switch(msg) {
197     case WM_CREATE:
198         /* create edit control */
199         hEdit = CreateWindowExA(0, "EDIT", "Some text", 0, 10, 10, 300, 300,
200                     hWnd, NULL, hinst, NULL);
201         ok(hEdit != NULL, "Can't create edit control\n");
202         break;
203     case WM_COMMAND:
204         if(HIWORD(wParam) == EN_KILLFOCUS)
205             killfocus_count++;
206         break;
207     }
208     return DefWindowProcA(hWnd, msg, wParam, lParam);
209 }
210 
211 static void createMainWnd(void)
212 {
213     WNDCLASSA wc;
214     wc.style = CS_HREDRAW | CS_VREDRAW;
215     wc.cbClsExtra = 0;
216     wc.cbWndExtra = 0;
217     wc.hInstance = GetModuleHandleA(NULL);
218     wc.hIcon = NULL;
219     wc.hCursor = LoadCursorA(NULL, (LPSTR)IDC_IBEAM);
220     wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
221     wc.lpszMenuName = NULL;
222     wc.lpszClassName = "MyTestWnd";
223     wc.lpfnWndProc = MyWndProc;
224     RegisterClassA(&wc);
225 
226     hMainWnd = CreateWindowExA(0, "MyTestWnd", "Blah", WS_OVERLAPPEDWINDOW,
227       CW_USEDEFAULT, CW_USEDEFAULT, 130, 105, NULL, NULL, GetModuleHandleA(NULL), 0);
228 }
229 
230 struct string_enumerator
231 {
232     IEnumString IEnumString_iface;
233     LONG ref;
234     WCHAR **data;
235     int data_len;
236     int cur;
237 };
238 
239 static struct string_enumerator *impl_from_IEnumString(IEnumString *iface)
240 {
241     return CONTAINING_RECORD(iface, struct string_enumerator, IEnumString_iface);
242 }
243 
244 static HRESULT WINAPI string_enumerator_QueryInterface(IEnumString *iface, REFIID riid, void **ppv)
245 {
246     if (IsEqualGUID(riid, &IID_IEnumString) || IsEqualGUID(riid, &IID_IUnknown))
247     {
248         IUnknown_AddRef(iface);
249         *ppv = iface;
250         return S_OK;
251     }
252 
253     *ppv = NULL;
254     return E_NOINTERFACE;
255 }
256 
257 static ULONG WINAPI string_enumerator_AddRef(IEnumString *iface)
258 {
259     struct string_enumerator *this = impl_from_IEnumString(iface);
260 
261     ULONG ref = InterlockedIncrement(&this->ref);
262 
263     return ref;
264 }
265 
266 static ULONG WINAPI string_enumerator_Release(IEnumString *iface)
267 {
268     struct string_enumerator *this = impl_from_IEnumString(iface);
269 
270     ULONG ref = InterlockedDecrement(&this->ref);
271 
272     if (!ref)
273         heap_free(this);
274 
275     return ref;
276 }
277 
278 static HRESULT WINAPI string_enumerator_Next(IEnumString *iface, ULONG num, LPOLESTR *strings, ULONG *num_returned)
279 {
280     struct string_enumerator *this = impl_from_IEnumString(iface);
281     int i, len;
282 
283     *num_returned = 0;
284     for (i = 0; i < num; i++)
285     {
286         if (this->cur >= this->data_len)
287             return S_FALSE;
288 
289         len = lstrlenW(this->data[this->cur]) + 1;
290 
291         strings[i] = CoTaskMemAlloc(len * sizeof(WCHAR));
292         memcpy(strings[i], this->data[this->cur], len * sizeof(WCHAR));
293 
294         (*num_returned)++;
295         this->cur++;
296     }
297 
298     return S_OK;
299 }
300 
301 static HRESULT WINAPI string_enumerator_Reset(IEnumString *iface)
302 {
303     struct string_enumerator *this = impl_from_IEnumString(iface);
304 
305     this->cur = 0;
306 
307     return S_OK;
308 }
309 
310 static HRESULT WINAPI string_enumerator_Skip(IEnumString *iface, ULONG num)
311 {
312     struct string_enumerator *this = impl_from_IEnumString(iface);
313 
314     this->cur += num;
315 
316     return S_OK;
317 }
318 
319 static HRESULT WINAPI string_enumerator_Clone(IEnumString *iface, IEnumString **out)
320 {
321     *out = NULL;
322     return E_NOTIMPL;
323 }
324 
325 static IEnumStringVtbl string_enumerator_vtlb =
326 {
327     string_enumerator_QueryInterface,
328     string_enumerator_AddRef,
329     string_enumerator_Release,
330     string_enumerator_Next,
331     string_enumerator_Skip,
332     string_enumerator_Reset,
333     string_enumerator_Clone
334 };
335 
336 static HRESULT string_enumerator_create(void **ppv, WCHAR **suggestions, int count)
337 {
338     struct string_enumerator *object;
339 
340     object = heap_alloc_zero(sizeof(*object));
341     object->IEnumString_iface.lpVtbl = &string_enumerator_vtlb;
342     object->ref = 1;
343     object->data = suggestions;
344     object->data_len = count;
345     object->cur = 0;
346 
347     *ppv = &object->IEnumString_iface;
348 
349     return S_OK;
350 }
351 
352 static void test_custom_source(void)
353 {
354     static WCHAR str_alpha[] = {'t','e','s','t','1',0};
355     static WCHAR str_alpha2[] = {'t','e','s','t','2',0};
356     static WCHAR str_beta[] = {'a','u','t','o',' ','c','o','m','p','l','e','t','e',0};
357     static WCHAR *suggestions[] = { str_alpha, str_alpha2, str_beta };
358     IUnknown *enumerator;
359     IAutoComplete2 *autocomplete;
360     HWND hwnd_edit;
361     WCHAR buffer[20];
362     HRESULT hr;
363     MSG msg;
364 
365     ShowWindow(hMainWnd, SW_SHOW);
366 
367     hwnd_edit = CreateWindowA("Edit", "", WS_OVERLAPPED | WS_VISIBLE | WS_CHILD | WS_BORDER, 50, 5, 200, 20, hMainWnd, 0, NULL, 0);
368 
369     hr = CoCreateInstance(&CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, &IID_IAutoComplete2, (void**)&autocomplete);
370     ok(hr == S_OK, "CoCreateInstance failed: %x\n", hr);
371 
372     string_enumerator_create((void**)&enumerator, suggestions, sizeof(suggestions) / sizeof(*suggestions));
373 
374     hr = IAutoComplete2_SetOptions(autocomplete, ACO_AUTOSUGGEST | ACO_AUTOAPPEND);
375     ok(hr == S_OK, "IAutoComplete2_SetOptions failed: %x\n", hr);
376     hr = IAutoComplete2_Init(autocomplete, hwnd_edit, enumerator, NULL, NULL);
377     ok(hr == S_OK, "IAutoComplete_Init failed: %x\n", hr);
378 
379     SendMessageW(hwnd_edit, WM_CHAR, 'a', 1);
380     /* Send a keyup message since wine doesn't handle WM_CHAR yet */
381     SendMessageW(hwnd_edit, WM_KEYUP, 'u', 1);
382     Sleep(100);
383     while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE))
384     {
385         TranslateMessage(&msg);
386         DispatchMessageA(&msg);
387     }
388     SendMessageW(hwnd_edit, WM_GETTEXT, sizeof(buffer) / sizeof(*buffer), (LPARAM)buffer);
389     ok(lstrcmpW(str_beta, buffer) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str_beta), wine_dbgstr_w(buffer));
390 
391     ShowWindow(hMainWnd, SW_HIDE);
392     DestroyWindow(hwnd_edit);
393 }
394 
395 START_TEST(autocomplete)
396 {
397     HRESULT r;
398     MSG msg;
399     IAutoComplete* ac;
400 
401     r = CoInitialize(NULL);
402     ok(r == S_OK, "CoInitialize failed (0x%08x). Tests aborted.\n", r);
403     if (r != S_OK)
404         return;
405 
406     createMainWnd();
407     ok(hMainWnd != NULL, "Failed to create parent window. Tests aborted.\n");
408     if (!hMainWnd) return;
409 
410     test_invalid_init();
411     ac = test_init();
412     if (!ac)
413         goto cleanup;
414     test_killfocus();
415 
416     test_custom_source();
417 
418     PostQuitMessage(0);
419     while(GetMessageA(&msg,0,0,0)) {
420         TranslateMessage(&msg);
421         DispatchMessageA(&msg);
422     }
423 
424     IAutoComplete_Release(ac);
425 
426 cleanup:
427     DestroyWindow(hEdit);
428     DestroyWindow(hMainWnd);
429 
430     CoUninitialize();
431 }
432