1 /* Unit tests for the task dialog control.
3 * Copyright 2017 Fabian Maurer for the Wine project
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.
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.
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
27 #include "wine/test.h"
31 #define WM_TD_CALLBACK (WM_APP) /* Custom dummy message to wrap callback notifications */
33 #define NUM_MSG_SEQUENCES 1
34 #define TASKDIALOG_SEQ_INDEX 0
36 #define TEST_NUM_BUTTONS 10 /* Number of custom buttons to test with */
37 #define TEST_NUM_RADIO_BUTTONS 3
39 #define ID_START 20 /* Lower IDs might be used by the system */
40 #define ID_START_BUTTON (ID_START + 0)
41 #define ID_START_RADIO_BUTTON (ID_START + 20)
43 static HRESULT (WINAPI
*pTaskDialogIndirect
)(const TASKDIALOGCONFIG
*, int *, int *, BOOL
*);
44 static HRESULT (WINAPI
*pTaskDialog
)(HWND
, HINSTANCE
, const WCHAR
*, const WCHAR
*, const WCHAR
*,
45 TASKDIALOG_COMMON_BUTTON_FLAGS
, const WCHAR
*, int *);
47 static struct msg_sequence
*sequences
[NUM_MSG_SEQUENCES
];
55 HRESULT callback_retval
;
56 const struct message_info
*send
; /* Message to send to trigger the next callback message */
59 static const struct message_info
*current_message_info
;
61 /* Messages to send */
62 static const struct message_info msg_send_return
[] =
64 { WM_KEYDOWN
, VK_RETURN
, 0 },
68 /* Messages to test against */
69 static const struct message_info msg_return_press_ok
[] =
71 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
72 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
76 static const struct message_info msg_return_press_yes
[] =
78 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
79 { TDN_BUTTON_CLICKED
, IDYES
, 0, S_OK
, NULL
},
83 static const struct message_info msg_return_press_no
[] =
85 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
86 { TDN_BUTTON_CLICKED
, IDNO
, 0, S_OK
, NULL
},
90 static const struct message_info msg_return_press_cancel
[] =
92 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
93 { TDN_BUTTON_CLICKED
, IDCANCEL
, 0, S_OK
, NULL
},
97 static const struct message_info msg_return_press_retry
[] =
99 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
100 { TDN_BUTTON_CLICKED
, IDRETRY
, 0, S_OK
, NULL
},
104 static const struct message_info msg_return_press_custom1
[] =
106 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
107 { TDN_BUTTON_CLICKED
, ID_START_BUTTON
, 0, S_OK
, NULL
},
111 static const struct message_info msg_return_press_custom4
[] =
113 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
114 { TDN_BUTTON_CLICKED
, ID_START_BUTTON
+ 3, 0, S_OK
, NULL
},
118 static const struct message_info msg_return_press_custom10
[] =
120 { TDN_CREATED
, 0, 0, S_OK
, msg_send_return
},
121 { TDN_BUTTON_CLICKED
, -1, 0, S_OK
, NULL
},
125 static const struct message_info msg_send_click_ok
[] =
127 { TDM_CLICK_BUTTON
, IDOK
, 0 },
131 static const struct message_info msg_send_f1
[] =
133 { WM_KEYF1
, 0, 0, 0},
137 static const struct message_info msg_got_tdn_help
[] =
139 { TDN_CREATED
, 0, 0, S_OK
, msg_send_f1
},
140 { TDN_HELP
, 0, 0, S_OK
, msg_send_click_ok
},
141 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
145 /* Three radio buttons */
146 static const struct message_info msg_return_default_radio_button_1
[] =
148 { TDN_CREATED
, 0, 0, S_OK
, NULL
},
149 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
, 0, S_OK
, msg_send_click_ok
},
150 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
154 static const struct message_info msg_return_default_radio_button_2
[] =
156 { TDN_CREATED
, 0, 0, S_OK
, NULL
},
157 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
+ 1, 0, S_OK
, msg_send_click_ok
},
158 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
162 static const struct message_info msg_return_default_radio_button_3
[] =
164 { TDN_CREATED
, 0, 0, S_OK
, NULL
},
165 { TDN_RADIO_BUTTON_CLICKED
, -2, 0, S_OK
, msg_send_click_ok
},
166 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
170 static const struct message_info msg_select_first_radio_button
[] =
172 { TDM_CLICK_RADIO_BUTTON
, ID_START_RADIO_BUTTON
, 0 },
176 static const struct message_info msg_return_first_radio_button
[] =
178 { TDN_CREATED
, 0, 0, S_OK
, NULL
},
179 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
+ 1, 0, S_OK
, msg_select_first_radio_button
},
180 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
, 0, S_OK
, msg_send_click_ok
},
181 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
185 static const struct message_info msg_select_first_disabled_radio_button_and_press_ok
[] =
187 { TDM_ENABLE_RADIO_BUTTON
, ID_START_RADIO_BUTTON
, 0 },
188 { TDM_CLICK_RADIO_BUTTON
, ID_START_RADIO_BUTTON
, 0 },
189 { TDM_CLICK_BUTTON
, IDOK
, 0 },
193 static const struct message_info msg_return_default_radio_button_clicking_disabled
[] =
195 { TDN_CREATED
, 0, 0, S_OK
, NULL
},
196 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
+ 1, 0, S_OK
, msg_select_first_disabled_radio_button_and_press_ok
},
197 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
, 0, S_OK
, NULL
},
198 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
202 static const struct message_info msg_return_no_default_radio_button_flag
[] =
204 { TDN_CREATED
, 0, 0, S_OK
, msg_send_click_ok
},
205 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
, 0, S_OK
, NULL
},
206 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
210 static const struct message_info msg_return_no_default_radio_button_id_and_flag
[] =
212 { TDN_CREATED
, 0, 0, S_OK
, msg_send_click_ok
},
213 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
217 static const struct message_info msg_select_negative_id_radio_button
[] =
219 { TDM_CLICK_RADIO_BUTTON
, -2, 0 },
223 static const struct message_info msg_return_press_negative_id_radio_button
[] =
225 { TDN_CREATED
, 0, 0, S_OK
, msg_select_negative_id_radio_button
},
226 { TDN_RADIO_BUTTON_CLICKED
, -2, 0, S_OK
, msg_send_click_ok
},
227 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
231 static const struct message_info msg_send_all_common_button_click
[] =
233 { TDM_CLICK_BUTTON
, IDOK
, 0 },
234 { TDM_CLICK_BUTTON
, IDYES
, 0 },
235 { TDM_CLICK_BUTTON
, IDNO
, 0 },
236 { TDM_CLICK_BUTTON
, IDCANCEL
, 0 },
237 { TDM_CLICK_BUTTON
, IDRETRY
, 0 },
238 { TDM_CLICK_BUTTON
, IDCLOSE
, 0 },
239 { TDM_CLICK_BUTTON
, ID_START_BUTTON
+ 99, 0 },
243 static const struct message_info msg_press_nonexistent_buttons
[] =
245 { TDN_CREATED
, 0, 0, S_OK
, msg_send_all_common_button_click
},
246 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_FALSE
, NULL
},
247 { TDN_BUTTON_CLICKED
, IDYES
, 0, S_FALSE
, NULL
},
248 { TDN_BUTTON_CLICKED
, IDNO
, 0, S_FALSE
, NULL
},
249 { TDN_BUTTON_CLICKED
, IDCANCEL
, 0, S_FALSE
, NULL
},
250 { TDN_BUTTON_CLICKED
, IDRETRY
, 0, S_FALSE
, NULL
},
251 { TDN_BUTTON_CLICKED
, IDCLOSE
, 0, S_FALSE
, NULL
},
252 { TDN_BUTTON_CLICKED
, ID_START_BUTTON
+ 99, 0, S_OK
, NULL
},
256 static const struct message_info msg_send_all_common_button_click_with_command
[] =
258 { WM_COMMAND
, MAKEWORD(IDOK
, BN_CLICKED
), 0 },
259 { WM_COMMAND
, MAKEWORD(IDYES
, BN_CLICKED
), 0 },
260 { WM_COMMAND
, MAKEWORD(IDNO
, BN_CLICKED
), 0 },
261 { WM_COMMAND
, MAKEWORD(IDCANCEL
, BN_CLICKED
), 0 },
262 { WM_COMMAND
, MAKEWORD(IDRETRY
, BN_CLICKED
), 0 },
263 { WM_COMMAND
, MAKEWORD(IDCLOSE
, BN_CLICKED
), 0 },
264 { WM_COMMAND
, MAKEWORD(ID_START_BUTTON
+ 99, BN_CLICKED
), 0 },
265 { WM_COMMAND
, MAKEWORD(IDOK
, BN_CLICKED
), 0 },
269 static const struct message_info msg_press_nonexistent_buttons_with_command
[] =
271 { TDN_CREATED
, 0, 0, S_OK
, msg_send_all_common_button_click_with_command
},
272 { TDN_BUTTON_CLICKED
, ID_START_BUTTON
, 0, S_FALSE
, NULL
},
273 { TDN_BUTTON_CLICKED
, ID_START_BUTTON
, 0, S_OK
, NULL
},
277 static const struct message_info msg_send_nonexistent_radio_button_click
[] =
279 { TDM_CLICK_RADIO_BUTTON
, ID_START_RADIO_BUTTON
+ 99, 0 },
280 { TDM_CLICK_BUTTON
, IDOK
, 0 },
284 static const struct message_info msg_press_nonexistent_radio_button
[] =
286 { TDN_CREATED
, 0, 0, S_OK
, msg_send_nonexistent_radio_button_click
},
287 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
291 static const struct message_info msg_return_default_verification_unchecked
[] =
293 { TDN_CREATED
, 0, 0, S_OK
, msg_send_click_ok
},
294 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
298 static const struct message_info msg_return_default_verification_checked
[] =
300 { TDN_CREATED
, 0, 0, S_OK
, msg_send_click_ok
},
301 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
305 static const struct message_info msg_uncheck_verification
[] =
307 { TDM_CLICK_VERIFICATION
, FALSE
, 0 },
311 static const struct message_info msg_return_verification_unchecked
[] =
313 { TDN_CREATED
, 0, 0, S_OK
, msg_uncheck_verification
},
314 { TDN_VERIFICATION_CLICKED
, FALSE
, 0, S_OK
, msg_send_click_ok
},
315 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
319 static const struct message_info msg_check_verification
[] =
321 { TDM_CLICK_VERIFICATION
, TRUE
, 0 },
325 static const struct message_info msg_return_verification_checked
[] =
327 { TDN_CREATED
, 0, 0, S_OK
, msg_check_verification
},
328 { TDN_VERIFICATION_CLICKED
, TRUE
, 0, S_OK
, msg_send_click_ok
},
329 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
333 static TASKDIALOGCONFIG navigated_info
= {0};
335 static const struct message_info msg_send_navigate
[] =
337 { TDM_NAVIGATE_PAGE
, 0, (LPARAM
)&navigated_info
, 0},
341 static const struct message_info msg_return_navigated_page
[] =
343 { TDN_CREATED
, 0, 0, S_OK
, NULL
},
344 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
, 0, S_OK
, msg_send_navigate
},
345 { TDN_DIALOG_CONSTRUCTED
, 0, 0, S_OK
, NULL
},
346 { TDN_RADIO_BUTTON_CLICKED
, ID_START_RADIO_BUTTON
, 0, S_OK
, NULL
},
347 { TDN_NAVIGATED
, 0, 0, S_OK
, msg_send_click_ok
},
348 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
352 static const struct message_info msg_send_close
[] =
354 { WM_CLOSE
, 0, 0, 0},
358 static const struct message_info msg_handle_wm_close
[] =
360 { TDN_CREATED
, 0, 0, S_OK
, msg_send_close
},
361 { TDN_BUTTON_CLICKED
, IDCANCEL
, 0, S_FALSE
, msg_send_close
},
362 { TDN_BUTTON_CLICKED
, IDCANCEL
, 0, S_OK
, NULL
},
366 static const struct message_info msg_send_close_then_ok
[] =
368 { WM_CLOSE
, 0, 0, 0},
369 { TDM_CLICK_BUTTON
, IDOK
, 0 },
373 static const struct message_info msg_handle_wm_close_without_cancel_button
[] =
375 { TDN_CREATED
, 0, 0, S_OK
, msg_send_close_then_ok
},
376 { TDN_BUTTON_CLICKED
, IDOK
, 0, S_OK
, NULL
},
380 static void init_test_message(UINT message
, WPARAM wParam
, LPARAM lParam
, struct message
*msg
)
382 msg
->message
= WM_TD_CALLBACK
;
383 msg
->flags
= sent
|wparam
|lparam
|id
;
384 msg
->wParam
= wParam
;
385 msg
->lParam
= lParam
;
390 #define run_test(info, expect_button, expect_radio_button, verification_checked, seq, context) \
391 run_test_(info, expect_button, expect_radio_button, verification_checked, seq, context, \
392 ARRAY_SIZE(seq) - 1, __FILE__, __LINE__)
394 static void run_test_(TASKDIALOGCONFIG
*info
, int expect_button
, int expect_radio_button
, BOOL verification_checked
,
395 const struct message_info
*test_messages
, const char *context
, int test_messages_len
,
396 const char *file
, int line
)
398 struct message
*msg
, *msg_start
;
401 BOOL ret_verification
= FALSE
;
405 /* Allocate messages to test against, plus 2 implicit and 1 empty */
406 msg_start
= msg
= calloc(test_messages_len
+ 3, sizeof(*msg
));
408 /* Always needed, thus made implicit */
409 init_test_message(TDN_DIALOG_CONSTRUCTED
, 0, 0, msg
++);
410 for (i
= 0; i
< test_messages_len
; i
++)
411 init_test_message(test_messages
[i
].message
, test_messages
[i
].wparam
, test_messages
[i
].lparam
, msg
++);
412 /* Always needed, thus made implicit */
413 init_test_message(TDN_DESTROYED
, 0, 0, msg
++);
415 current_message_info
= test_messages
;
416 flush_sequences(sequences
, NUM_MSG_SEQUENCES
);
418 hr
= pTaskDialogIndirect(info
, &ret_button
, &ret_radio
, &ret_verification
);
419 ok_(file
, line
)(hr
== S_OK
, "TaskDialogIndirect() failed, got %#lx.\n", hr
);
421 ok_sequence_(sequences
, TASKDIALOG_SEQ_INDEX
, msg_start
, context
, FALSE
, file
, line
);
422 ok_(file
, line
)(ret_button
== expect_button
,
423 "Wrong button. Expected %d, got %d\n", expect_button
, ret_button
);
424 ok_(file
, line
)(ret_radio
== expect_radio_button
,
425 "Wrong radio button. Expected %d, got %d\n", expect_radio_button
, ret_radio
);
430 static const LONG_PTR test_ref_data
= 123456;
432 static HRESULT CALLBACK
taskdialog_callback_proc(HWND hwnd
, UINT notification
,
433 WPARAM wParam
, LPARAM lParam
, LONG_PTR ref_data
)
435 int msg_pos
= sequences
[TASKDIALOG_SEQ_INDEX
]->count
- 1; /* Skip implicit message */
436 const struct message_info
*msg_send
;
439 ok(test_ref_data
== ref_data
, "Unexpected ref data %Iu.\n", ref_data
);
441 init_test_message(notification
, (short)wParam
, lParam
, &msg
);
442 add_message(sequences
, TASKDIALOG_SEQ_INDEX
, &msg
);
444 if (notification
== TDN_DIALOG_CONSTRUCTED
|| notification
== TDN_DESTROYED
) /* Skip implicit messages */
447 msg_send
= current_message_info
[msg_pos
].send
;
448 for(; msg_send
&& msg_send
->message
; msg_send
++)
449 PostMessageW(hwnd
, msg_send
->message
, msg_send
->wparam
, msg_send
->lparam
);
451 return current_message_info
[msg_pos
].callback_retval
;
454 static void test_invalid_parameters(void)
456 TASKDIALOGCONFIG info
= { 0 };
459 hr
= pTaskDialogIndirect(NULL
, NULL
, NULL
, NULL
);
460 ok(hr
== E_INVALIDARG
, "Unexpected return value %#lx.\n", hr
);
463 hr
= pTaskDialogIndirect(&info
, NULL
, NULL
, NULL
);
464 ok(hr
== E_INVALIDARG
, "Unexpected return value %#lx.\n", hr
);
466 info
.cbSize
= sizeof(TASKDIALOGCONFIG
) - 1;
467 hr
= pTaskDialogIndirect(&info
, NULL
, NULL
, NULL
);
468 ok(hr
== E_INVALIDARG
, "Unexpected return value %#lx.\n", hr
);
470 info
.cbSize
= sizeof(TASKDIALOGCONFIG
) + 1;
471 hr
= pTaskDialogIndirect(&info
, NULL
, NULL
, NULL
);
472 ok(hr
== E_INVALIDARG
, "Unexpected return value %#lx.\n", hr
);
475 static void test_callback(void)
477 TASKDIALOGCONFIG info
= {0};
479 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
480 info
.pfCallback
= taskdialog_callback_proc
;
481 info
.lpCallbackData
= test_ref_data
;
483 run_test(&info
, IDOK
, 0, FALSE
, msg_return_press_ok
, "Press VK_RETURN.");
486 static void test_buttons(void)
488 TASKDIALOGCONFIG info
= {0};
489 static const DWORD command_link_flags
[] = {0, TDF_USE_COMMAND_LINKS
, TDF_USE_COMMAND_LINKS_NO_ICON
};
490 TASKDIALOG_BUTTON custom_buttons
[TEST_NUM_BUTTONS
], radio_buttons
[TEST_NUM_RADIO_BUTTONS
];
491 /* Each button has two digits as title, plus null-terminator */
492 WCHAR button_titles
[TEST_NUM_BUTTONS
* 3], radio_button_titles
[TEST_NUM_BUTTONS
* 3];
495 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
496 info
.pfCallback
= taskdialog_callback_proc
;
497 info
.lpCallbackData
= test_ref_data
;
499 /* Init custom buttons */
500 for (i
= 0; i
< TEST_NUM_BUTTONS
; i
++)
502 WCHAR
*text
= &button_titles
[i
* 3];
503 wsprintfW(text
, L
"%02d", i
);
505 custom_buttons
[i
].pszButtonText
= text
;
506 custom_buttons
[i
].nButtonID
= ID_START_BUTTON
+ i
;
508 custom_buttons
[TEST_NUM_BUTTONS
- 1].nButtonID
= -1;
510 /* Init radio buttons */
511 for (i
= 0; i
< TEST_NUM_RADIO_BUTTONS
; i
++)
513 WCHAR
*text
= &radio_button_titles
[i
* 3];
514 wsprintfW(text
, L
"%02d", i
);
516 radio_buttons
[i
].pszButtonText
= text
;
517 radio_buttons
[i
].nButtonID
= ID_START_RADIO_BUTTON
+ i
;
519 radio_buttons
[TEST_NUM_RADIO_BUTTONS
- 1].nButtonID
= -2;
521 /* Test nDefaultButton */
523 /* Test common buttons with invalid default ID */
524 info
.nDefaultButton
= 0; /* Should default to first created button */
525 info
.dwCommonButtons
= TDCBF_OK_BUTTON
| TDCBF_YES_BUTTON
| TDCBF_NO_BUTTON
526 | TDCBF_CANCEL_BUTTON
| TDCBF_RETRY_BUTTON
| TDCBF_CLOSE_BUTTON
;
527 run_test(&info
, IDOK
, 0, FALSE
, msg_return_press_ok
, "default button: unset default");
528 info
.dwCommonButtons
= TDCBF_YES_BUTTON
| TDCBF_NO_BUTTON
529 | TDCBF_CANCEL_BUTTON
| TDCBF_RETRY_BUTTON
| TDCBF_CLOSE_BUTTON
;
530 run_test(&info
, IDYES
, 0, FALSE
, msg_return_press_yes
, "default button: unset default");
531 info
.dwCommonButtons
= TDCBF_NO_BUTTON
| TDCBF_CANCEL_BUTTON
| TDCBF_RETRY_BUTTON
| TDCBF_CLOSE_BUTTON
;
532 run_test(&info
, IDNO
, 0, FALSE
, msg_return_press_no
, "default button: unset default");
533 info
.dwCommonButtons
= TDCBF_CANCEL_BUTTON
| TDCBF_RETRY_BUTTON
| TDCBF_CLOSE_BUTTON
;
534 run_test(&info
, IDRETRY
, 0, FALSE
, msg_return_press_retry
, "default button: unset default");
535 info
.dwCommonButtons
= TDCBF_CANCEL_BUTTON
| TDCBF_CLOSE_BUTTON
;
536 run_test(&info
, IDCANCEL
, 0, FALSE
, msg_return_press_cancel
, "default button: unset default");
538 /* Custom buttons could be command links */
539 for (i
= 0; i
< ARRAY_SIZE(command_link_flags
); i
++)
541 info
.dwFlags
= command_link_flags
[i
];
543 /* Test with all common and custom buttons and invalid default ID */
544 info
.nDefaultButton
= 0xff; /* Random ID, should also default to first created button */
545 info
.cButtons
= TEST_NUM_BUTTONS
;
546 info
.pButtons
= custom_buttons
;
547 run_test(&info
, ID_START_BUTTON
, 0, FALSE
, msg_return_press_custom1
,
548 "default button: invalid default, with common buttons - 1");
550 info
.nDefaultButton
= -1; /* Should work despite button ID -1 */
551 run_test(&info
, -1, 0, FALSE
, msg_return_press_custom10
, "default button: invalid default, with common buttons - 2");
553 info
.nDefaultButton
= -2; /* Should also default to first created button */
554 run_test(&info
, ID_START_BUTTON
, 0, FALSE
, msg_return_press_custom1
,
555 "default button: invalid default, with common buttons - 3");
557 /* Test with only custom buttons and invalid default ID */
558 info
.dwCommonButtons
= 0;
559 run_test(&info
, ID_START_BUTTON
, 0, FALSE
, msg_return_press_custom1
,
560 "default button: invalid default, no common buttons");
562 /* Test with common and custom buttons and valid default ID */
563 info
.dwCommonButtons
= TDCBF_OK_BUTTON
| TDCBF_YES_BUTTON
| TDCBF_NO_BUTTON
| TDCBF_CANCEL_BUTTON
564 | TDCBF_RETRY_BUTTON
| TDCBF_CLOSE_BUTTON
;
565 info
.nDefaultButton
= IDRETRY
;
566 run_test(&info
, IDRETRY
, 0, FALSE
, msg_return_press_retry
, "default button: valid default - 1");
568 /* Test with common and custom buttons and valid default ID */
569 info
.nDefaultButton
= ID_START_BUTTON
+ 3;
570 run_test(&info
, ID_START_BUTTON
+ 3, 0, FALSE
, msg_return_press_custom4
, "default button: valid default - 2");
573 /* Test radio buttons */
574 info
.nDefaultButton
= 0;
577 info
.dwCommonButtons
= TDCBF_OK_BUTTON
;
578 info
.cRadioButtons
= TEST_NUM_RADIO_BUTTONS
;
579 info
.pRadioButtons
= radio_buttons
;
581 /* Test default first radio button */
582 run_test(&info
, IDOK
, ID_START_RADIO_BUTTON
, FALSE
, msg_return_default_radio_button_1
,
583 "default radio button: default first radio button");
585 /* Test default radio button */
586 info
.nDefaultRadioButton
= ID_START_RADIO_BUTTON
+ 1;
587 run_test(&info
, IDOK
, info
.nDefaultRadioButton
, FALSE
, msg_return_default_radio_button_2
,
588 "default radio button: default radio button");
590 /* Test default radio button with -2 */
591 info
.nDefaultRadioButton
= -2;
592 run_test(&info
, IDOK
, info
.nDefaultRadioButton
, FALSE
, msg_return_default_radio_button_3
,
593 "default radio button: default radio button with id -2");
595 /* Test default radio button after clicking the first, messages still work even radio button is disabled */
596 info
.nDefaultRadioButton
= ID_START_RADIO_BUTTON
+ 1;
597 run_test(&info
, IDOK
, ID_START_RADIO_BUTTON
, FALSE
, msg_return_first_radio_button
,
598 "default radio button: radio button after clicking");
600 /* Test radio button after disabling and clicking the first */
601 info
.nDefaultRadioButton
= ID_START_RADIO_BUTTON
+ 1;
602 run_test(&info
, IDOK
, ID_START_RADIO_BUTTON
, FALSE
, msg_return_default_radio_button_clicking_disabled
,
603 "default radio button: disable radio button before clicking");
605 /* 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 */
606 info
.nDefaultRadioButton
= ID_START_RADIO_BUTTON
;
607 info
.dwFlags
= TDF_NO_DEFAULT_RADIO_BUTTON
;
608 run_test(&info
, IDOK
, info
.nDefaultRadioButton
, FALSE
, msg_return_no_default_radio_button_flag
,
609 "default radio button: no default radio flag");
611 /* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is 0.
612 * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */
613 info
.nDefaultRadioButton
= 0;
614 info
.dwFlags
= TDF_NO_DEFAULT_RADIO_BUTTON
;
615 run_test(&info
, IDOK
, 0, FALSE
, msg_return_no_default_radio_button_id_and_flag
,
616 "default radio button: no default radio id and flag");
618 /* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is invalid.
619 * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */
620 info
.nDefaultRadioButton
= 0xff;
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 flag, invalid id");
625 info
.nDefaultRadioButton
= 0;
626 info
.dwFlags
= TDF_NO_DEFAULT_RADIO_BUTTON
;
627 run_test(&info
, IDOK
, -2, FALSE
, msg_return_press_negative_id_radio_button
,
628 "radio button: manually click radio button with negative id");
630 /* Test sending clicks to non-existent buttons. Notification of non-existent buttons will be sent */
631 info
.cButtons
= TEST_NUM_BUTTONS
;
632 info
.pButtons
= custom_buttons
;
633 info
.cRadioButtons
= TEST_NUM_RADIO_BUTTONS
;
634 info
.pRadioButtons
= radio_buttons
;
635 info
.dwCommonButtons
= 0;
636 info
.dwFlags
= TDF_NO_DEFAULT_RADIO_BUTTON
;
637 run_test(&info
, ID_START_BUTTON
+ 99, 0, FALSE
, msg_press_nonexistent_buttons
, "sends click to non-existent buttons");
639 /* Non-existent button clicks sent by WM_COMMAND won't generate TDN_BUTTON_CLICKED except IDOK.
640 * And will get the first existent button identifier instead of IDOK */
641 run_test(&info
, ID_START_BUTTON
, 0, FALSE
, msg_press_nonexistent_buttons_with_command
,
642 "sends click to non-existent buttons with WM_COMMAND");
644 /* Non-existent radio button won't get notifications */
645 run_test(&info
, IDOK
, 0, FALSE
, msg_press_nonexistent_radio_button
, "sends click to non-existent radio buttons");
648 static void test_help(void)
650 TASKDIALOGCONFIG info
= {0};
652 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
653 info
.pfCallback
= taskdialog_callback_proc
;
654 info
.lpCallbackData
= test_ref_data
;
655 info
.dwCommonButtons
= TDCBF_OK_BUTTON
;
657 run_test(&info
, IDOK
, 0, FALSE
, msg_got_tdn_help
, "send f1");
660 struct timer_notification_data
662 DWORD last_elapsed_ms
;
666 static HRESULT CALLBACK
taskdialog_callback_proc_timer(HWND hwnd
, UINT notification
,
667 WPARAM wParam
, LPARAM lParam
, LONG_PTR ref_data
)
669 struct timer_notification_data
*data
= (struct timer_notification_data
*)ref_data
;
671 if (notification
== TDN_TIMER
)
676 elapsed_ms
= (DWORD
)wParam
;
678 if (data
->num_fired
== 3)
679 ok(data
->last_elapsed_ms
> elapsed_ms
, "Expected reference time update.\n");
682 delta
= elapsed_ms
- data
->last_elapsed_ms
;
683 ok(delta
> 0, "Expected positive time tick difference.\n");
685 data
->last_elapsed_ms
= elapsed_ms
;
687 if (data
->num_fired
== 3)
688 PostMessageW(hwnd
, TDM_CLICK_BUTTON
, IDOK
, 0);
691 return data
->num_fired
== 3 ? S_FALSE
: S_OK
;
697 static void test_timer(void)
699 struct timer_notification_data data
= { 0 };
700 TASKDIALOGCONFIG info
= { 0 };
702 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
703 info
.pfCallback
= taskdialog_callback_proc_timer
;
704 info
.lpCallbackData
= (LONG_PTR
)&data
;
705 info
.dwFlags
= TDF_CALLBACK_TIMER
;
706 info
.dwCommonButtons
= TDCBF_OK_BUTTON
;
708 pTaskDialogIndirect(&info
, NULL
, NULL
, NULL
);
711 static HRESULT CALLBACK
taskdialog_callback_proc_progress_bar(HWND hwnd
, UINT notification
, WPARAM wParam
,
712 LPARAM lParam
, LONG_PTR ref_data
)
715 LONG flags
= (LONG
)ref_data
;
716 if (notification
== TDN_CREATED
)
718 /* TDM_SET_PROGRESS_BAR_STATE */
719 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_STATE
, PBST_NORMAL
, 0);
720 ok(ret
== PBST_NORMAL
, "Expect state: %d got state: %lx\n", PBST_NORMAL
, ret
);
721 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_STATE
, PBST_PAUSED
, 0);
722 ok(ret
== PBST_NORMAL
, "Expect state: %d got state: %lx\n", PBST_NORMAL
, ret
);
723 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_STATE
, PBST_ERROR
, 0);
724 /* Progress bar has fixme on handling PBM_SETSTATE message */
725 todo_wine
ok(ret
== PBST_PAUSED
, "Expect state: %d got state: %lx\n", PBST_PAUSED
, ret
);
726 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_STATE
, PBST_NORMAL
, 0);
727 todo_wine
ok(ret
== PBST_ERROR
, "Expect state: %d got state: %lx\n", PBST_ERROR
, ret
);
729 /* TDM_SET_PROGRESS_BAR_RANGE */
730 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_RANGE
, 0, MAKELPARAM(0, 200));
731 ok(ret
== MAKELONG(0, 100), "Expect range:%lx got:%lx\n", MAKELONG(0, 100), ret
);
732 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_RANGE
, 0, MAKELPARAM(0, 200));
733 ok(ret
== MAKELONG(0, 200), "Expect range:%lx got:%lx\n", MAKELONG(0, 200), ret
);
735 /* TDM_SET_PROGRESS_BAR_POS */
736 if (flags
& TDF_SHOW_MARQUEE_PROGRESS_BAR
)
738 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_POS
, 1, 0);
739 ok(ret
== 0, "Expect position:%x got:%lx\n", 0, ret
);
740 ret
= SendMessageW(hwnd
, TDM_SET_PROGRESS_BAR_POS
, 2, 0);
741 ok(ret
== 0, "Expect position:%x got:%lx\n", 0, ret
);
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
== 1, "Expect position:%x got:%lx\n", 1, ret
);
751 SendMessageW(hwnd
, TDM_CLICK_BUTTON
, IDOK
, 0);
757 static void test_progress_bar(void)
759 TASKDIALOGCONFIG info
= {0};
761 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
762 info
.dwFlags
= TDF_SHOW_PROGRESS_BAR
;
763 info
.pfCallback
= taskdialog_callback_proc_progress_bar
;
764 info
.lpCallbackData
= (LONG_PTR
)info
.dwFlags
;
765 info
.dwCommonButtons
= TDCBF_OK_BUTTON
;
766 pTaskDialogIndirect(&info
, NULL
, NULL
, NULL
);
768 info
.dwFlags
= TDF_SHOW_MARQUEE_PROGRESS_BAR
;
769 info
.lpCallbackData
= (LONG_PTR
)info
.dwFlags
;
770 pTaskDialogIndirect(&info
, NULL
, NULL
, NULL
);
772 info
.dwFlags
= TDF_SHOW_PROGRESS_BAR
| TDF_SHOW_MARQUEE_PROGRESS_BAR
;
773 info
.lpCallbackData
= (LONG_PTR
)info
.dwFlags
;
774 pTaskDialogIndirect(&info
, NULL
, NULL
, NULL
);
777 static void test_verification_box(void)
779 TASKDIALOGCONFIG info
= {0};
781 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
782 info
.pfCallback
= taskdialog_callback_proc
;
783 info
.lpCallbackData
= test_ref_data
;
784 info
.dwCommonButtons
= TDCBF_OK_BUTTON
;
786 /* TDF_VERIFICATION_FLAG_CHECKED works even if pszVerificationText is not set */
787 run_test(&info
, IDOK
, 0, FALSE
, msg_return_default_verification_unchecked
, "default verification box: unchecked");
789 info
.dwFlags
= TDF_VERIFICATION_FLAG_CHECKED
;
790 run_test(&info
, IDOK
, 0, FALSE
, msg_return_default_verification_checked
, "default verification box: checked");
792 info
.pszVerificationText
= L
"text";
793 run_test(&info
, IDOK
, 0, FALSE
, msg_return_default_verification_unchecked
, "default verification box: unchecked");
795 info
.dwFlags
= TDF_VERIFICATION_FLAG_CHECKED
;
796 run_test(&info
, IDOK
, 0, FALSE
, msg_return_default_verification_checked
, "default verification box: checked");
798 run_test(&info
, IDOK
, 0, FALSE
, msg_return_verification_unchecked
,
799 "default verification box: default checked and then unchecked");
802 run_test(&info
, IDOK
, 0, FALSE
, msg_return_verification_checked
,
803 "default verification box: default unchecked and then checked");
806 static void test_navigate_page(void)
808 TASKDIALOGCONFIG info
= {0};
809 TASKDIALOG_BUTTON radio_buttons
[TEST_NUM_RADIO_BUTTONS
];
810 WCHAR radio_button_titles
[TEST_NUM_BUTTONS
* 3];
813 /* Init radio buttons */
814 for (i
= 0; i
< TEST_NUM_RADIO_BUTTONS
; i
++)
816 WCHAR
*text
= &radio_button_titles
[i
* 3];
817 wsprintfW(text
, L
"%02d", i
);
819 radio_buttons
[i
].pszButtonText
= text
;
820 radio_buttons
[i
].nButtonID
= ID_START_RADIO_BUTTON
+ i
;
823 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
824 info
.pfCallback
= taskdialog_callback_proc
;
825 info
.lpCallbackData
= test_ref_data
;
826 info
.dwCommonButtons
= TDCBF_OK_BUTTON
;
827 info
.cRadioButtons
= TEST_NUM_RADIO_BUTTONS
;
828 info
.pRadioButtons
= radio_buttons
;
830 navigated_info
= info
;
831 navigated_info
.pszVerificationText
= L
"text";
832 navigated_info
.dwFlags
= TDF_VERIFICATION_FLAG_CHECKED
;
834 run_test(&info
, IDOK
, ID_START_RADIO_BUTTON
, TRUE
, msg_return_navigated_page
, "navigate page: default");
836 /* TDM_NAVIGATE_PAGE doesn't check cbSize.
837 * And null taskconfig pointer crash applicatioin, thus doesn't check pointer either */
838 navigated_info
.cbSize
= 0;
839 run_test(&info
, IDOK
, ID_START_RADIO_BUTTON
, TRUE
, msg_return_navigated_page
, "navigate page: invalid taskconfig cbSize");
842 static void test_wm_close(void)
844 TASKDIALOGCONFIG info
= {0};
846 info
.cbSize
= sizeof(TASKDIALOGCONFIG
);
847 info
.pfCallback
= taskdialog_callback_proc
;
848 info
.lpCallbackData
= test_ref_data
;
850 /* WM_CLOSE can end the dialog only when a cancel button is present or dwFlags has TDF_ALLOW_DIALOG_CANCELLATION */
851 info
.dwCommonButtons
= TDCBF_OK_BUTTON
;
852 run_test(&info
, IDOK
, 0, FALSE
, msg_handle_wm_close_without_cancel_button
, "send WM_CLOSE without cancel button");
854 info
.dwFlags
= TDF_ALLOW_DIALOG_CANCELLATION
;
855 run_test(&info
, IDCANCEL
, 0, FALSE
, msg_handle_wm_close
, "send WM_CLOSE with TDF_ALLOW_DIALOG_CANCELLATION");
858 info
.dwCommonButtons
= TDCBF_CANCEL_BUTTON
;
859 run_test(&info
, IDCANCEL
, 0, FALSE
, msg_handle_wm_close
, "send WM_CLOSE with a cancel button");
862 START_TEST(taskdialog
)
864 ULONG_PTR ctx_cookie
;
869 if (!load_v6_module(&ctx_cookie
, &hCtx
))
872 /* Check if task dialogs are available */
873 hinst
= LoadLibraryA("comctl32.dll");
875 pTaskDialogIndirect
= (void *)GetProcAddress(hinst
, "TaskDialogIndirect");
876 if (!pTaskDialogIndirect
)
878 win_skip("TaskDialogIndirect not exported by name\n");
879 unload_v6_module(ctx_cookie
, hCtx
);
883 pTaskDialog
= (void *)GetProcAddress(hinst
, "TaskDialog");
885 ptr_ordinal
= GetProcAddress(hinst
, (const char *)344);
886 ok(pTaskDialog
== ptr_ordinal
, "got wrong pointer for ordinal 344, %p expected %p\n",
887 ptr_ordinal
, pTaskDialog
);
889 ptr_ordinal
= GetProcAddress(hinst
, (const char *)345);
890 ok(pTaskDialogIndirect
== ptr_ordinal
, "got wrong pointer for ordinal 345, %p expected %p\n",
891 ptr_ordinal
, pTaskDialogIndirect
);
893 init_msg_sequences(sequences
, NUM_MSG_SEQUENCES
);
895 test_invalid_parameters();
901 test_verification_box();
902 test_navigate_page();
905 unload_v6_module(ctx_cookie
, hCtx
);