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
init_test_message(UINT message,WPARAM wParam,LPARAM lParam,struct message * msg)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
run_test_(TASKDIALOGCONFIG * info,int expect_button,int expect_radio_button,BOOL verification_checked,const struct message_info * test_messages,const char * context,int test_messages_len,const char * file,int line)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
taskdialog_callback_proc(HWND hwnd,UINT notification,WPARAM wParam,LPARAM lParam,LONG_PTR ref_data)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
test_invalid_parameters(void)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
test_callback(void)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
test_buttons(void)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
test_help(void)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
taskdialog_callback_proc_timer(HWND hwnd,UINT notification,WPARAM wParam,LPARAM lParam,LONG_PTR ref_data)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
test_timer(void)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
taskdialog_callback_proc_progress_bar(HWND hwnd,UINT notification,WPARAM wParam,LPARAM lParam,LONG_PTR ref_data)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
test_progress_bar(void)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
test_verification_box(void)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
test_navigate_page(void)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
test_wm_close(void)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
START_TEST(taskdialog)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