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