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