1 /* Unit tests for the task dialog control.
2  *
3  * Copyright 2017 Fabian Maurer for the Wine project
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include <stdarg.h>
21 
22 #include "windef.h"
23 #include "winbase.h"
24 #include "winuser.h"
25 #include "commctrl.h"
26 
27 #include "wine/heap.h"
28 #include "wine/test.h"
29 #include "v6util.h"
30 #include "msg.h"
31 
32 #ifdef __REACTOS__
33 #undef WM_KEYF1
34 #define WM_KEYF1 0x004d
35 #endif
36 
37 #define WM_TD_CALLBACK (WM_APP) /* Custom dummy message to wrap callback notifications */
38 
39 #define NUM_MSG_SEQUENCES     1
40 #define TASKDIALOG_SEQ_INDEX  0
41 
42 #define TEST_NUM_BUTTONS 10 /* Number of custom buttons to test with */
43 #define TEST_NUM_RADIO_BUTTONS 3
44 
45 #define ID_START 20 /* Lower IDs might be used by the system */
46 #define ID_START_BUTTON (ID_START + 0)
47 #define ID_START_RADIO_BUTTON (ID_START + 20)
48 
49 static HRESULT (WINAPI *pTaskDialogIndirect)(const TASKDIALOGCONFIG *, int *, int *, BOOL *);
50 static HRESULT (WINAPI *pTaskDialog)(HWND, HINSTANCE, const WCHAR *, const WCHAR *, const WCHAR *,
51         TASKDIALOG_COMMON_BUTTON_FLAGS, const WCHAR *, int *);
52 
53 static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];
54 
55 struct message_info
56 {
57     UINT message;
58     WPARAM wparam;
59     LPARAM lparam;
60 
61     HRESULT callback_retval;
62     const struct message_info *send;  /* Message to send to trigger the next callback message */
63 };
64 
65 static const struct message_info *current_message_info;
66 
67 /* Messages to send */
68 static const struct message_info msg_send_return[] =
69 {
70     { WM_KEYDOWN, VK_RETURN, 0 },
71     { 0 }
72 };
73 
74 /* Messages to test against */
75 static const struct message_info msg_return_press_ok[] =
76 {
77     { TDN_CREATED,        0,    0, S_OK, msg_send_return },
78     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
79     { 0 }
80 };
81 
82 static const struct message_info msg_return_press_yes[] =
83 {
84     { TDN_CREATED,        0,    0, S_OK, msg_send_return },
85     { TDN_BUTTON_CLICKED, IDYES, 0, S_OK, NULL },
86     { 0 }
87 };
88 
89 static const struct message_info msg_return_press_no[] =
90 {
91     { TDN_CREATED,        0,    0, S_OK, msg_send_return },
92     { TDN_BUTTON_CLICKED, IDNO, 0, S_OK, NULL },
93     { 0 }
94 };
95 
96 static const struct message_info msg_return_press_cancel[] =
97 {
98     { TDN_CREATED,        0,    0, S_OK, msg_send_return },
99     { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_OK, NULL },
100     { 0 }
101 };
102 
103 static const struct message_info msg_return_press_retry[] =
104 {
105     { TDN_CREATED,        0,       0, S_OK, msg_send_return },
106     { TDN_BUTTON_CLICKED, IDRETRY, 0, S_OK, NULL },
107     { 0 }
108 };
109 
110 static const struct message_info msg_return_press_custom1[] =
111 {
112     { TDN_CREATED,        0,               0, S_OK, msg_send_return },
113     { TDN_BUTTON_CLICKED, ID_START_BUTTON, 0, S_OK, NULL },
114     { 0 }
115 };
116 
117 static const struct message_info msg_return_press_custom4[] =
118 {
119     { TDN_CREATED,        0,                   0, S_OK, msg_send_return },
120     { TDN_BUTTON_CLICKED, ID_START_BUTTON + 3, 0, S_OK, NULL },
121     { 0 }
122 };
123 
124 static const struct message_info msg_return_press_custom10[] =
125 {
126     { TDN_CREATED,        0,                   0, S_OK, msg_send_return },
127     { TDN_BUTTON_CLICKED, -1, 0, S_OK, NULL },
128     { 0 }
129 };
130 
131 static const struct message_info msg_send_click_ok[] =
132 {
133     { TDM_CLICK_BUTTON, IDOK, 0 },
134     { 0 }
135 };
136 
137 static const struct message_info msg_send_f1[] =
138 {
139     { WM_KEYF1, 0, 0, 0},
140     { 0 }
141 };
142 
143 static const struct message_info msg_got_tdn_help[] =
144 {
145     { TDN_CREATED, 0, 0, S_OK, msg_send_f1 },
146     { TDN_HELP, 0, 0, S_OK, msg_send_click_ok },
147     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
148     { 0 }
149 };
150 
151 /* Three radio buttons */
152 static const struct message_info msg_return_default_radio_button_1[] =
153 {
154     { TDN_CREATED, 0, 0, S_OK, NULL },
155     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, msg_send_click_ok },
156     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
157     { 0 }
158 };
159 
160 static const struct message_info msg_return_default_radio_button_2[] =
161 {
162     { TDN_CREATED, 0, 0, S_OK, NULL },
163     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON + 1, 0, S_OK, msg_send_click_ok },
164     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
165     { 0 }
166 };
167 
168 static const struct message_info msg_return_default_radio_button_3[] =
169 {
170     { TDN_CREATED, 0, 0, S_OK, NULL },
171     { TDN_RADIO_BUTTON_CLICKED, -2, 0, S_OK, msg_send_click_ok },
172     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
173     { 0 }
174 };
175 
176 static const struct message_info msg_select_first_radio_button[] =
177 {
178     { TDM_CLICK_RADIO_BUTTON, ID_START_RADIO_BUTTON, 0 },
179     { 0 }
180 };
181 
182 static const struct message_info msg_return_first_radio_button[] =
183 {
184     { TDN_CREATED, 0, 0, S_OK, NULL },
185     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON + 1, 0, S_OK, msg_select_first_radio_button },
186     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, msg_send_click_ok },
187     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
188     { 0 }
189 };
190 
191 static const struct message_info msg_select_first_disabled_radio_button_and_press_ok[] =
192 {
193     { TDM_ENABLE_RADIO_BUTTON, ID_START_RADIO_BUTTON, 0 },
194     { TDM_CLICK_RADIO_BUTTON, ID_START_RADIO_BUTTON, 0 },
195     { TDM_CLICK_BUTTON, IDOK, 0 },
196     { 0 }
197 };
198 
199 static const struct message_info msg_return_default_radio_button_clicking_disabled[] =
200 {
201     { TDN_CREATED, 0, 0, S_OK, NULL },
202     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON + 1, 0, S_OK, msg_select_first_disabled_radio_button_and_press_ok },
203     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, NULL },
204     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
205     { 0 }
206 };
207 
208 static const struct message_info msg_return_no_default_radio_button_flag[] =
209 {
210     { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok },
211     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, NULL },
212     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
213     { 0 }
214 };
215 
216 static const struct message_info msg_return_no_default_radio_button_id_and_flag[] =
217 {
218     { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok },
219     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
220     { 0 }
221 };
222 
223 static const struct message_info msg_select_negative_id_radio_button[] =
224 {
225     { TDM_CLICK_RADIO_BUTTON, -2, 0 },
226     { 0 }
227 };
228 
229 static const struct message_info msg_return_press_negative_id_radio_button[] =
230 {
231     { TDN_CREATED, 0, 0, S_OK, msg_select_negative_id_radio_button },
232     { TDN_RADIO_BUTTON_CLICKED, -2, 0, S_OK, msg_send_click_ok },
233     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
234     { 0 }
235 };
236 
237 static const struct message_info msg_send_all_common_button_click[] =
238 {
239     { TDM_CLICK_BUTTON, IDOK, 0 },
240     { TDM_CLICK_BUTTON, IDYES, 0 },
241     { TDM_CLICK_BUTTON, IDNO, 0 },
242     { TDM_CLICK_BUTTON, IDCANCEL, 0 },
243     { TDM_CLICK_BUTTON, IDRETRY, 0 },
244     { TDM_CLICK_BUTTON, IDCLOSE, 0 },
245     { TDM_CLICK_BUTTON, ID_START_BUTTON + 99, 0 },
246     { 0 }
247 };
248 
249 static const struct message_info msg_press_nonexistent_buttons[] =
250 {
251     { TDN_CREATED, 0, 0, S_OK, msg_send_all_common_button_click },
252     { TDN_BUTTON_CLICKED, IDOK, 0, S_FALSE, NULL },
253     { TDN_BUTTON_CLICKED, IDYES, 0, S_FALSE, NULL },
254     { TDN_BUTTON_CLICKED, IDNO, 0, S_FALSE, NULL },
255     { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_FALSE, NULL },
256     { TDN_BUTTON_CLICKED, IDRETRY, 0, S_FALSE, NULL },
257     { TDN_BUTTON_CLICKED, IDCLOSE, 0, S_FALSE, NULL },
258     { TDN_BUTTON_CLICKED, ID_START_BUTTON + 99, 0, S_OK, NULL },
259     { 0 }
260 };
261 
262 static const struct message_info msg_send_all_common_button_click_with_command[] =
263 {
264     { WM_COMMAND, MAKEWORD(IDOK, BN_CLICKED), 0 },
265     { WM_COMMAND, MAKEWORD(IDYES, BN_CLICKED), 0 },
266     { WM_COMMAND, MAKEWORD(IDNO, BN_CLICKED), 0 },
267     { WM_COMMAND, MAKEWORD(IDCANCEL, BN_CLICKED), 0 },
268     { WM_COMMAND, MAKEWORD(IDRETRY, BN_CLICKED), 0 },
269     { WM_COMMAND, MAKEWORD(IDCLOSE, BN_CLICKED), 0 },
270     { WM_COMMAND, MAKEWORD(ID_START_BUTTON + 99, BN_CLICKED), 0 },
271     { WM_COMMAND, MAKEWORD(IDOK, BN_CLICKED), 0 },
272     { 0 }
273 };
274 
275 static const struct message_info msg_press_nonexistent_buttons_with_command[] =
276 {
277     { TDN_CREATED, 0, 0, S_OK, msg_send_all_common_button_click_with_command },
278     { TDN_BUTTON_CLICKED, ID_START_BUTTON, 0, S_FALSE, NULL },
279     { TDN_BUTTON_CLICKED, ID_START_BUTTON, 0, S_OK, NULL },
280     { 0 }
281 };
282 
283 static const struct message_info msg_send_nonexistent_radio_button_click[] =
284 {
285     { TDM_CLICK_RADIO_BUTTON, ID_START_RADIO_BUTTON + 99, 0 },
286     { TDM_CLICK_BUTTON, IDOK, 0 },
287     { 0 }
288 };
289 
290 static const struct message_info msg_press_nonexistent_radio_button[] =
291 {
292     { TDN_CREATED, 0, 0, S_OK, msg_send_nonexistent_radio_button_click },
293     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
294     { 0 }
295 };
296 
297 static const struct message_info msg_return_default_verification_unchecked[] =
298 {
299     { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok },
300     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
301     { 0 }
302 };
303 
304 static const struct message_info msg_return_default_verification_checked[] =
305 {
306     { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok },
307     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
308     { 0 }
309 };
310 
311 static const struct message_info msg_uncheck_verification[] =
312 {
313     { TDM_CLICK_VERIFICATION, FALSE, 0 },
314     { 0 }
315 };
316 
317 static const struct message_info msg_return_verification_unchecked[] =
318 {
319     { TDN_CREATED, 0, 0, S_OK, msg_uncheck_verification },
320     { TDN_VERIFICATION_CLICKED, FALSE, 0, S_OK, msg_send_click_ok },
321     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
322     { 0 }
323 };
324 
325 static const struct message_info msg_check_verification[] =
326 {
327     { TDM_CLICK_VERIFICATION, TRUE, 0 },
328     { 0 }
329 };
330 
331 static const struct message_info msg_return_verification_checked[] =
332 {
333     { TDN_CREATED, 0, 0, S_OK, msg_check_verification },
334     { TDN_VERIFICATION_CLICKED, TRUE, 0, S_OK, msg_send_click_ok },
335     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
336     { 0 }
337 };
338 
339 static TASKDIALOGCONFIG navigated_info = {0};
340 
341 static const struct message_info msg_send_navigate[] =
342 {
343     { TDM_NAVIGATE_PAGE, 0, (LPARAM)&navigated_info, 0},
344     { 0 }
345 };
346 
347 static const struct message_info msg_return_navigated_page[] =
348 {
349     { TDN_CREATED, 0, 0, S_OK, NULL },
350     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, msg_send_navigate },
351     { TDN_DIALOG_CONSTRUCTED, 0, 0, S_OK, NULL },
352     { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, NULL },
353     { TDN_NAVIGATED, 0, 0, S_OK, msg_send_click_ok },
354     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
355     { 0 }
356 };
357 
358 static const struct message_info msg_send_close[] =
359 {
360     { WM_CLOSE, 0, 0, 0},
361     { 0 }
362 };
363 
364 static const struct message_info msg_handle_wm_close[] =
365 {
366     { TDN_CREATED, 0, 0, S_OK, msg_send_close },
367     { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_FALSE, msg_send_close },
368     { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_OK, NULL },
369     { 0 }
370 };
371 
372 static const struct message_info msg_send_close_then_ok[] =
373 {
374     { WM_CLOSE, 0, 0, 0},
375     { TDM_CLICK_BUTTON, IDOK, 0 },
376     { 0 }
377 };
378 
379 static const struct message_info msg_handle_wm_close_without_cancel_button[] =
380 {
381     { TDN_CREATED, 0, 0, S_OK, msg_send_close_then_ok },
382     { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL },
383     { 0 }
384 };
385 
386 static void init_test_message(UINT message, WPARAM wParam, LPARAM lParam, struct message *msg)
387 {
388     msg->message = WM_TD_CALLBACK;
389     msg->flags = sent|wparam|lparam|id;
390     msg->wParam = wParam;
391     msg->lParam = lParam;
392     msg->id = message;
393     msg->stage = 0;
394 }
395 
396 #define run_test(info, expect_button, expect_radio_button, verification_checked, seq, context) \
397         run_test_(info, expect_button, expect_radio_button, verification_checked, seq, context, \
398                    ARRAY_SIZE(seq) - 1, __FILE__, __LINE__)
399 
400 static void run_test_(TASKDIALOGCONFIG *info, int expect_button, int expect_radio_button, BOOL verification_checked,
401                       const struct message_info *test_messages, const char *context, int test_messages_len,
402                       const char *file, int line)
403 {
404     struct message *msg, *msg_start;
405     int ret_button = 0;
406     int ret_radio = 0;
407     BOOL ret_verification = FALSE;
408     HRESULT hr;
409     int i;
410 
411     /* Allocate messages to test against, plus 2 implicit and 1 empty */
412     msg_start = msg = heap_alloc_zero(sizeof(*msg) * (test_messages_len + 3));
413 
414     /* Always needed, thus made implicit */
415     init_test_message(TDN_DIALOG_CONSTRUCTED, 0, 0, msg++);
416     for (i = 0; i < test_messages_len; i++)
417         init_test_message(test_messages[i].message, test_messages[i].wparam, test_messages[i].lparam, msg++);
418     /* Always needed, thus made implicit */
419     init_test_message(TDN_DESTROYED, 0, 0, msg++);
420 
421     current_message_info = test_messages;
422     flush_sequences(sequences, NUM_MSG_SEQUENCES);
423 
424     hr = pTaskDialogIndirect(info, &ret_button, &ret_radio, &ret_verification);
425     ok_(file, line)(hr == S_OK, "TaskDialogIndirect() failed, got %#x.\n", hr);
426 
427     ok_sequence_(sequences, TASKDIALOG_SEQ_INDEX, msg_start, context, FALSE, file, line);
428     ok_(file, line)(ret_button == expect_button,
429                      "Wrong button. Expected %d, got %d\n", expect_button, ret_button);
430     ok_(file, line)(ret_radio == expect_radio_button,
431                      "Wrong radio button. Expected %d, got %d\n", expect_radio_button, ret_radio);
432 
433     heap_free(msg_start);
434 }
435 
436 static const LONG_PTR test_ref_data = 123456;
437 
438 static HRESULT CALLBACK taskdialog_callback_proc(HWND hwnd, UINT notification,
439     WPARAM wParam, LPARAM lParam, LONG_PTR ref_data)
440 {
441     int msg_pos = sequences[TASKDIALOG_SEQ_INDEX]->count - 1; /* Skip implicit message */
442     const struct message_info *msg_send;
443     struct message msg;
444 
445     ok(test_ref_data == ref_data, "Unexpected ref data %lu.\n", ref_data);
446 
447     init_test_message(notification, (short)wParam, lParam, &msg);
448     add_message(sequences, TASKDIALOG_SEQ_INDEX, &msg);
449 
450     if (notification == TDN_DIALOG_CONSTRUCTED || notification == TDN_DESTROYED) /* Skip implicit messages */
451         return S_OK;
452 
453     msg_send = current_message_info[msg_pos].send;
454     for(; msg_send && msg_send->message; msg_send++)
455         PostMessageW(hwnd, msg_send->message, msg_send->wparam, msg_send->lparam);
456 
457     return current_message_info[msg_pos].callback_retval;
458 }
459 
460 static void test_invalid_parameters(void)
461 {
462     TASKDIALOGCONFIG info = { 0 };
463     HRESULT hr;
464 
465     hr = pTaskDialogIndirect(NULL, NULL, NULL, NULL);
466     ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr);
467 
468     info.cbSize = 0;
469     hr = pTaskDialogIndirect(&info, NULL, NULL, NULL);
470     ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr);
471 
472     info.cbSize = sizeof(TASKDIALOGCONFIG) - 1;
473     hr = pTaskDialogIndirect(&info, NULL, NULL, NULL);
474     ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr);
475 
476     info.cbSize = sizeof(TASKDIALOGCONFIG) + 1;
477     hr = pTaskDialogIndirect(&info, NULL, NULL, NULL);
478     ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr);
479 }
480 
481 static void test_callback(void)
482 {
483     TASKDIALOGCONFIG info = {0};
484 
485     info.cbSize = sizeof(TASKDIALOGCONFIG);
486     info.pfCallback = taskdialog_callback_proc;
487     info.lpCallbackData = test_ref_data;
488 
489     run_test(&info, IDOK, 0, FALSE, msg_return_press_ok, "Press VK_RETURN.");
490 }
491 
492 static void test_buttons(void)
493 {
494     TASKDIALOGCONFIG info = {0};
495     static const DWORD command_link_flags[] = {0, TDF_USE_COMMAND_LINKS, TDF_USE_COMMAND_LINKS_NO_ICON};
496     TASKDIALOG_BUTTON custom_buttons[TEST_NUM_BUTTONS], radio_buttons[TEST_NUM_RADIO_BUTTONS];
497     const WCHAR button_format[] = {'%','0','2','d',0};
498     /* Each button has two digits as title, plus null-terminator */
499     WCHAR button_titles[TEST_NUM_BUTTONS * 3], radio_button_titles[TEST_NUM_BUTTONS * 3];
500     int i;
501 
502     info.cbSize = sizeof(TASKDIALOGCONFIG);
503     info.pfCallback = taskdialog_callback_proc;
504     info.lpCallbackData = test_ref_data;
505 
506     /* Init custom buttons */
507     for (i = 0; i < TEST_NUM_BUTTONS; i++)
508     {
509         WCHAR *text = &button_titles[i * 3];
510         wsprintfW(text, button_format, i);
511 
512         custom_buttons[i].pszButtonText = text;
513         custom_buttons[i].nButtonID = ID_START_BUTTON + i;
514     }
515     custom_buttons[TEST_NUM_BUTTONS - 1].nButtonID = -1;
516 
517     /* Init radio buttons */
518     for (i = 0; i < TEST_NUM_RADIO_BUTTONS; i++)
519     {
520         WCHAR *text = &radio_button_titles[i * 3];
521         wsprintfW(text, button_format, i);
522 
523         radio_buttons[i].pszButtonText = text;
524         radio_buttons[i].nButtonID = ID_START_RADIO_BUTTON + i;
525     }
526     radio_buttons[TEST_NUM_RADIO_BUTTONS - 1].nButtonID = -2;
527 
528     /* Test nDefaultButton */
529 
530     /* Test common buttons with invalid default ID */
531     info.nDefaultButton = 0; /* Should default to first created button */
532     info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON
533             | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON;
534     run_test(&info, IDOK, 0, FALSE, msg_return_press_ok, "default button: unset default");
535     info.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON
536             | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON;
537     run_test(&info, IDYES, 0, FALSE, msg_return_press_yes, "default button: unset default");
538     info.dwCommonButtons = TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON;
539     run_test(&info, IDNO, 0, FALSE, msg_return_press_no, "default button: unset default");
540     info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON;
541     run_test(&info, IDRETRY, 0, FALSE, msg_return_press_retry, "default button: unset default");
542     info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON;
543     run_test(&info, IDCANCEL, 0, FALSE, msg_return_press_cancel, "default button: unset default");
544 
545     /* Custom buttons could be command links */
546     for (i = 0; i < ARRAY_SIZE(command_link_flags); i++)
547     {
548         info.dwFlags = command_link_flags[i];
549 
550         /* Test with all common and custom buttons and invalid default ID */
551         info.nDefaultButton = 0xff; /* Random ID, should also default to first created button */
552         info.cButtons = TEST_NUM_BUTTONS;
553         info.pButtons = custom_buttons;
554         run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1,
555                  "default button: invalid default, with common buttons - 1");
556 
557         info.nDefaultButton = -1; /* Should work despite button ID -1 */
558         run_test(&info, -1, 0, FALSE, msg_return_press_custom10, "default button: invalid default, with common buttons - 2");
559 
560         info.nDefaultButton = -2; /* Should also default to first created button */
561         run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1,
562                  "default button: invalid default, with common buttons - 3");
563 
564         /* Test with only custom buttons and invalid default ID */
565         info.dwCommonButtons = 0;
566         run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1,
567                  "default button: invalid default, no common buttons");
568 
569         /* Test with common and custom buttons and valid default ID */
570         info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON
571                                | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON;
572         info.nDefaultButton = IDRETRY;
573         run_test(&info, IDRETRY, 0, FALSE, msg_return_press_retry, "default button: valid default - 1");
574 
575         /* Test with common and custom buttons and valid default ID */
576         info.nDefaultButton = ID_START_BUTTON + 3;
577         run_test(&info, ID_START_BUTTON + 3, 0, FALSE, msg_return_press_custom4, "default button: valid default - 2");
578     }
579 
580     /* Test radio buttons */
581     info.nDefaultButton = 0;
582     info.cButtons = 0;
583     info.pButtons = 0;
584     info.dwCommonButtons = TDCBF_OK_BUTTON;
585     info.cRadioButtons = TEST_NUM_RADIO_BUTTONS;
586     info.pRadioButtons = radio_buttons;
587 
588     /* Test default first radio button */
589     run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_default_radio_button_1,
590              "default radio button: default first radio button");
591 
592     /* Test default radio button */
593     info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1;
594     run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_default_radio_button_2,
595              "default radio button: default radio button");
596 
597     /* Test default radio button with -2 */
598     info.nDefaultRadioButton = -2;
599     run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_default_radio_button_3,
600              "default radio button: default radio button with id -2");
601 
602     /* Test default radio button after clicking the first, messages still work even radio button is disabled */
603     info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1;
604     run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_first_radio_button,
605              "default radio button: radio button after clicking");
606 
607     /* Test radio button after disabling and clicking the first */
608     info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1;
609     run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_default_radio_button_clicking_disabled,
610              "default radio button: disable radio button before clicking");
611 
612     /* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set, TDN_RADIO_BUTTON_CLICKED will still be received, just radio button not selected */
613     info.nDefaultRadioButton = ID_START_RADIO_BUTTON;
614     info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON;
615     run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_no_default_radio_button_flag,
616              "default radio button: no default radio flag");
617 
618     /* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is 0.
619      * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */
620     info.nDefaultRadioButton = 0;
621     info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON;
622     run_test(&info, IDOK, 0, FALSE, msg_return_no_default_radio_button_id_and_flag,
623              "default radio button: no default radio id and flag");
624 
625     /* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is invalid.
626      * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */
627     info.nDefaultRadioButton = 0xff;
628     info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON;
629     run_test(&info, IDOK, 0, FALSE, msg_return_no_default_radio_button_id_and_flag,
630              "default radio button: no default flag, invalid id");
631 
632     info.nDefaultRadioButton = 0;
633     info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON;
634     run_test(&info, IDOK, -2, FALSE, msg_return_press_negative_id_radio_button,
635              "radio button: manually click radio button with negative id");
636 
637     /* Test sending clicks to non-existent buttons. Notification of non-existent buttons will be sent */
638     info.cButtons = TEST_NUM_BUTTONS;
639     info.pButtons = custom_buttons;
640     info.cRadioButtons = TEST_NUM_RADIO_BUTTONS;
641     info.pRadioButtons = radio_buttons;
642     info.dwCommonButtons = 0;
643     info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON;
644     run_test(&info, ID_START_BUTTON + 99, 0, FALSE, msg_press_nonexistent_buttons, "sends click to non-existent buttons");
645 
646     /* Non-existent button clicks sent by WM_COMMAND won't generate TDN_BUTTON_CLICKED except IDOK.
647      * And will get the first existent button identifier instead of IDOK */
648     run_test(&info, ID_START_BUTTON, 0, FALSE, msg_press_nonexistent_buttons_with_command,
649              "sends click to non-existent buttons with WM_COMMAND");
650 
651     /* Non-existent radio button won't get notifications */
652     run_test(&info, IDOK, 0, FALSE, msg_press_nonexistent_radio_button, "sends click to non-existent radio buttons");
653 }
654 
655 static void test_help(void)
656 {
657     TASKDIALOGCONFIG info = {0};
658 
659     info.cbSize = sizeof(TASKDIALOGCONFIG);
660     info.pfCallback = taskdialog_callback_proc;
661     info.lpCallbackData = test_ref_data;
662     info.dwCommonButtons = TDCBF_OK_BUTTON;
663 
664     run_test(&info, IDOK, 0, FALSE, msg_got_tdn_help, "send f1");
665 }
666 
667 struct timer_notification_data
668 {
669     DWORD last_elapsed_ms;
670     DWORD num_fired;
671 };
672 
673 static HRESULT CALLBACK taskdialog_callback_proc_timer(HWND hwnd, UINT notification,
674         WPARAM wParam, LPARAM lParam, LONG_PTR ref_data)
675 {
676     struct timer_notification_data *data = (struct timer_notification_data *)ref_data;
677 
678     if (notification == TDN_TIMER)
679     {
680         DWORD elapsed_ms;
681         int delta;
682 
683         elapsed_ms = (DWORD)wParam;
684 
685         if (data->num_fired == 3)
686             ok(data->last_elapsed_ms > elapsed_ms, "Expected reference time update.\n");
687         else
688         {
689             delta = elapsed_ms - data->last_elapsed_ms;
690             ok(delta > 0, "Expected positive time tick difference.\n");
691         }
692         data->last_elapsed_ms = elapsed_ms;
693 
694         if (data->num_fired == 3)
695             PostMessageW(hwnd, TDM_CLICK_BUTTON, IDOK, 0);
696 
697         ++data->num_fired;
698         return data->num_fired == 3 ? S_FALSE : S_OK;
699     }
700 
701     return S_OK;
702 }
703 
704 static void test_timer(void)
705 {
706     struct timer_notification_data data = { 0 };
707     TASKDIALOGCONFIG info = { 0 };
708 
709     info.cbSize = sizeof(TASKDIALOGCONFIG);
710     info.pfCallback = taskdialog_callback_proc_timer;
711     info.lpCallbackData = (LONG_PTR)&data;
712     info.dwFlags = TDF_CALLBACK_TIMER;
713     info.dwCommonButtons = TDCBF_OK_BUTTON;
714 
715     pTaskDialogIndirect(&info, NULL, NULL, NULL);
716 }
717 
718 static HRESULT CALLBACK taskdialog_callback_proc_progress_bar(HWND hwnd, UINT notification, WPARAM wParam,
719                                                               LPARAM lParam, LONG_PTR ref_data)
720 {
721     unsigned long ret;
722     LONG flags = (LONG)ref_data;
723     if (notification == TDN_CREATED)
724     {
725         /* TDM_SET_PROGRESS_BAR_STATE */
726         ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_NORMAL, 0);
727         ok(ret == PBST_NORMAL, "Expect state: %d got state: %lx\n", PBST_NORMAL, ret);
728         ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_PAUSED, 0);
729         ok(ret == PBST_NORMAL, "Expect state: %d got state: %lx\n", PBST_NORMAL, ret);
730         ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_ERROR, 0);
731         /* Progress bar has fixme on handling PBM_SETSTATE message */
732         todo_wine ok(ret == PBST_PAUSED, "Expect state: %d got state: %lx\n", PBST_PAUSED, ret);
733         ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_NORMAL, 0);
734         todo_wine ok(ret == PBST_ERROR, "Expect state: %d got state: %lx\n", PBST_ERROR, ret);
735 
736         /* TDM_SET_PROGRESS_BAR_RANGE */
737         ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_RANGE, 0, MAKELPARAM(0, 200));
738         ok(ret == MAKELONG(0, 100), "Expect range:%x got:%lx\n", MAKELONG(0, 100), ret);
739         ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_RANGE, 0, MAKELPARAM(0, 200));
740         ok(ret == MAKELONG(0, 200), "Expect range:%x got:%lx\n", MAKELONG(0, 200), ret);
741 
742         /* TDM_SET_PROGRESS_BAR_POS */
743         if (flags & TDF_SHOW_MARQUEE_PROGRESS_BAR)
744         {
745             ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 1, 0);
746             ok(ret == 0, "Expect position:%x got:%lx\n", 0, ret);
747             ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 2, 0);
748             ok(ret == 0, "Expect position:%x got:%lx\n", 0, ret);
749         }
750         else
751         {
752             ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 1, 0);
753             ok(ret == 0, "Expect position:%x got:%lx\n", 0, ret);
754             ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 2, 0);
755             ok(ret == 1, "Expect position:%x got:%lx\n", 1, ret);
756         }
757 
758         SendMessageW(hwnd, TDM_CLICK_BUTTON, IDOK, 0);
759     }
760 
761     return S_OK;
762 }
763 
764 static void test_progress_bar(void)
765 {
766     TASKDIALOGCONFIG info = {0};
767 
768     info.cbSize = sizeof(TASKDIALOGCONFIG);
769     info.dwFlags = TDF_SHOW_PROGRESS_BAR;
770     info.pfCallback = taskdialog_callback_proc_progress_bar;
771     info.lpCallbackData = (LONG_PTR)info.dwFlags;
772     info.dwCommonButtons = TDCBF_OK_BUTTON;
773     pTaskDialogIndirect(&info, NULL, NULL, NULL);
774 
775     info.dwFlags = TDF_SHOW_MARQUEE_PROGRESS_BAR;
776     info.lpCallbackData = (LONG_PTR)info.dwFlags;
777     pTaskDialogIndirect(&info, NULL, NULL, NULL);
778 
779     info.dwFlags = TDF_SHOW_PROGRESS_BAR | TDF_SHOW_MARQUEE_PROGRESS_BAR;
780     info.lpCallbackData = (LONG_PTR)info.dwFlags;
781     pTaskDialogIndirect(&info, NULL, NULL, NULL);
782 }
783 
784 static void test_verification_box(void)
785 {
786     TASKDIALOGCONFIG info = {0};
787     WCHAR textW[] = {'t', 'e', 'x', 't', 0};
788 
789     info.cbSize = sizeof(TASKDIALOGCONFIG);
790     info.pfCallback = taskdialog_callback_proc;
791     info.lpCallbackData = test_ref_data;
792     info.dwCommonButtons = TDCBF_OK_BUTTON;
793 
794     /* TDF_VERIFICATION_FLAG_CHECKED works even if pszVerificationText is not set */
795     run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_unchecked, "default verification box: unchecked");
796 
797     info.dwFlags = TDF_VERIFICATION_FLAG_CHECKED;
798     run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_checked, "default verification box: checked");
799 
800     info.pszVerificationText = textW;
801     run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_unchecked, "default verification box: unchecked");
802 
803     info.dwFlags = TDF_VERIFICATION_FLAG_CHECKED;
804     run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_checked, "default verification box: checked");
805 
806     run_test(&info, IDOK, 0, FALSE, msg_return_verification_unchecked,
807              "default verification box: default checked and then unchecked");
808 
809     info.dwFlags = 0;
810     run_test(&info, IDOK, 0, FALSE, msg_return_verification_checked,
811              "default verification box: default unchecked and then checked");
812 }
813 
814 static void test_navigate_page(void)
815 {
816     TASKDIALOGCONFIG info = {0};
817     static const WCHAR textW[] = {'t', 'e', 'x', 't', 0};
818     static const WCHAR button_format[] = {'%', '0', '2', 'd', 0};
819     TASKDIALOG_BUTTON radio_buttons[TEST_NUM_RADIO_BUTTONS];
820     WCHAR radio_button_titles[TEST_NUM_BUTTONS * 3];
821     int i;
822 
823     /* Init radio buttons */
824     for (i = 0; i < TEST_NUM_RADIO_BUTTONS; i++)
825     {
826         WCHAR *text = &radio_button_titles[i * 3];
827         wsprintfW(text, button_format, i);
828 
829         radio_buttons[i].pszButtonText = text;
830         radio_buttons[i].nButtonID = ID_START_RADIO_BUTTON + i;
831     }
832 
833     info.cbSize = sizeof(TASKDIALOGCONFIG);
834     info.pfCallback = taskdialog_callback_proc;
835     info.lpCallbackData = test_ref_data;
836     info.dwCommonButtons = TDCBF_OK_BUTTON;
837     info.cRadioButtons = TEST_NUM_RADIO_BUTTONS;
838     info.pRadioButtons = radio_buttons;
839 
840     navigated_info = info;
841     navigated_info.pszVerificationText = textW;
842     navigated_info.dwFlags = TDF_VERIFICATION_FLAG_CHECKED;
843 
844     run_test(&info, IDOK, ID_START_RADIO_BUTTON, TRUE, msg_return_navigated_page, "navigate page: default");
845 
846     /* TDM_NAVIGATE_PAGE doesn't check cbSize.
847      * And null taskconfig pointer crash applicatioin, thus doesn't check pointer either */
848     navigated_info.cbSize = 0;
849     run_test(&info, IDOK, ID_START_RADIO_BUTTON, TRUE, msg_return_navigated_page, "navigate page: invalid taskconfig cbSize");
850 }
851 
852 static void test_wm_close(void)
853 {
854     TASKDIALOGCONFIG info = {0};
855 
856     info.cbSize = sizeof(TASKDIALOGCONFIG);
857     info.pfCallback = taskdialog_callback_proc;
858     info.lpCallbackData = test_ref_data;
859 
860     /* WM_CLOSE can end the dialog only when a cancel button is present or dwFlags has TDF_ALLOW_DIALOG_CANCELLATION */
861     info.dwCommonButtons = TDCBF_OK_BUTTON;
862     run_test(&info, IDOK, 0, FALSE, msg_handle_wm_close_without_cancel_button, "send WM_CLOSE without cancel button");
863 
864     info.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION;
865     run_test(&info, IDCANCEL, 0, FALSE, msg_handle_wm_close, "send WM_CLOSE with TDF_ALLOW_DIALOG_CANCELLATION");
866 
867     info.dwFlags = 0;
868     info.dwCommonButtons = TDCBF_CANCEL_BUTTON;
869     run_test(&info, IDCANCEL, 0, FALSE, msg_handle_wm_close, "send WM_CLOSE with a cancel button");
870 }
871 
872 START_TEST(taskdialog)
873 {
874     ULONG_PTR ctx_cookie;
875     void *ptr_ordinal;
876     HINSTANCE hinst;
877     HANDLE hCtx;
878 
879     if (!load_v6_module(&ctx_cookie, &hCtx))
880         return;
881 
882     /* Check if task dialogs are available */
883     hinst = LoadLibraryA("comctl32.dll");
884 
885     pTaskDialogIndirect = (void *)GetProcAddress(hinst, "TaskDialogIndirect");
886     if (!pTaskDialogIndirect)
887     {
888         win_skip("TaskDialogIndirect not exported by name\n");
889         unload_v6_module(ctx_cookie, hCtx);
890         return;
891     }
892 
893     pTaskDialog = (void *)GetProcAddress(hinst, "TaskDialog");
894 
895     ptr_ordinal = GetProcAddress(hinst, (const char *)344);
896     ok(pTaskDialog == ptr_ordinal, "got wrong pointer for ordinal 344, %p expected %p\n",
897                                             ptr_ordinal, pTaskDialog);
898 
899     ptr_ordinal = GetProcAddress(hinst, (const char *)345);
900     ok(pTaskDialogIndirect == ptr_ordinal, "got wrong pointer for ordinal 345, %p expected %p\n",
901                                             ptr_ordinal, pTaskDialogIndirect);
902 
903     init_msg_sequences(sequences, NUM_MSG_SEQUENCES);
904 
905     test_invalid_parameters();
906     test_callback();
907     test_buttons();
908     test_help();
909     test_timer();
910     test_progress_bar();
911     test_verification_box();
912     test_navigate_page();
913     test_wm_close();
914 
915     unload_v6_module(ctx_cookie, hCtx);
916 }
917