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