| /* Unit tests for the task dialog control. | |
| * | |
| * Copyright 2017 Fabian Maurer for the Wine project | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include <stdarg.h> | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winuser.h" | |
| #include "commctrl.h" | |
| #include "wine/test.h" | |
| #include "v6util.h" | |
| #include "msg.h" | |
| #define WM_TD_CALLBACK (WM_APP) /* Custom dummy message to wrap callback notifications */ | |
| #define NUM_MSG_SEQUENCES 1 | |
| #define TASKDIALOG_SEQ_INDEX 0 | |
| #define TEST_NUM_BUTTONS 10 /* Number of custom buttons to test with */ | |
| #define TEST_NUM_RADIO_BUTTONS 3 | |
| #define ID_START 20 /* Lower IDs might be used by the system */ | |
| #define ID_START_BUTTON (ID_START + 0) | |
| #define ID_START_RADIO_BUTTON (ID_START + 20) | |
| static HRESULT (WINAPI *pTaskDialogIndirect)(const TASKDIALOGCONFIG *, int *, int *, BOOL *); | |
| static HRESULT (WINAPI *pTaskDialog)(HWND, HINSTANCE, const WCHAR *, const WCHAR *, const WCHAR *, | |
| TASKDIALOG_COMMON_BUTTON_FLAGS, const WCHAR *, int *); | |
| static struct msg_sequence *sequences[NUM_MSG_SEQUENCES]; | |
| struct message_info | |
| { | |
| UINT message; | |
| WPARAM wparam; | |
| LPARAM lparam; | |
| HRESULT callback_retval; | |
| const struct message_info *send; /* Message to send to trigger the next callback message */ | |
| }; | |
| static const struct message_info *current_message_info; | |
| /* Messages to send */ | |
| static const struct message_info msg_send_return[] = | |
| { | |
| { WM_KEYDOWN, VK_RETURN, 0 }, | |
| { 0 } | |
| }; | |
| /* Messages to test against */ | |
| static const struct message_info msg_return_press_ok[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_yes[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, IDYES, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_no[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, IDNO, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_cancel[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_retry[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, IDRETRY, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_custom1[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, ID_START_BUTTON, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_custom4[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, ID_START_BUTTON + 3, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_custom10[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_return }, | |
| { TDN_BUTTON_CLICKED, -1, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_send_click_ok[] = | |
| { | |
| { TDM_CLICK_BUTTON, IDOK, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_send_f1[] = | |
| { | |
| { WM_KEYF1, 0, 0, 0}, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_got_tdn_help[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_f1 }, | |
| { TDN_HELP, 0, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| /* Three radio buttons */ | |
| static const struct message_info msg_return_default_radio_button_1[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, NULL }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_default_radio_button_2[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, NULL }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON + 1, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_default_radio_button_3[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, NULL }, | |
| { TDN_RADIO_BUTTON_CLICKED, -2, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_select_first_radio_button[] = | |
| { | |
| { TDM_CLICK_RADIO_BUTTON, ID_START_RADIO_BUTTON, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_first_radio_button[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, NULL }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON + 1, 0, S_OK, msg_select_first_radio_button }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_select_first_disabled_radio_button_and_press_ok[] = | |
| { | |
| { TDM_ENABLE_RADIO_BUTTON, ID_START_RADIO_BUTTON, 0 }, | |
| { TDM_CLICK_RADIO_BUTTON, ID_START_RADIO_BUTTON, 0 }, | |
| { TDM_CLICK_BUTTON, IDOK, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_default_radio_button_clicking_disabled[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, NULL }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON + 1, 0, S_OK, msg_select_first_disabled_radio_button_and_press_ok }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, NULL }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_no_default_radio_button_flag[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, NULL }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_no_default_radio_button_id_and_flag[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_select_negative_id_radio_button[] = | |
| { | |
| { TDM_CLICK_RADIO_BUTTON, -2, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_press_negative_id_radio_button[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_select_negative_id_radio_button }, | |
| { TDN_RADIO_BUTTON_CLICKED, -2, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_send_all_common_button_click[] = | |
| { | |
| { TDM_CLICK_BUTTON, IDOK, 0 }, | |
| { TDM_CLICK_BUTTON, IDYES, 0 }, | |
| { TDM_CLICK_BUTTON, IDNO, 0 }, | |
| { TDM_CLICK_BUTTON, IDCANCEL, 0 }, | |
| { TDM_CLICK_BUTTON, IDRETRY, 0 }, | |
| { TDM_CLICK_BUTTON, IDCLOSE, 0 }, | |
| { TDM_CLICK_BUTTON, ID_START_BUTTON + 99, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_press_nonexistent_buttons[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_all_common_button_click }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_FALSE, NULL }, | |
| { TDN_BUTTON_CLICKED, IDYES, 0, S_FALSE, NULL }, | |
| { TDN_BUTTON_CLICKED, IDNO, 0, S_FALSE, NULL }, | |
| { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_FALSE, NULL }, | |
| { TDN_BUTTON_CLICKED, IDRETRY, 0, S_FALSE, NULL }, | |
| { TDN_BUTTON_CLICKED, IDCLOSE, 0, S_FALSE, NULL }, | |
| { TDN_BUTTON_CLICKED, ID_START_BUTTON + 99, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_send_all_common_button_click_with_command[] = | |
| { | |
| { WM_COMMAND, MAKEWORD(IDOK, BN_CLICKED), 0 }, | |
| { WM_COMMAND, MAKEWORD(IDYES, BN_CLICKED), 0 }, | |
| { WM_COMMAND, MAKEWORD(IDNO, BN_CLICKED), 0 }, | |
| { WM_COMMAND, MAKEWORD(IDCANCEL, BN_CLICKED), 0 }, | |
| { WM_COMMAND, MAKEWORD(IDRETRY, BN_CLICKED), 0 }, | |
| { WM_COMMAND, MAKEWORD(IDCLOSE, BN_CLICKED), 0 }, | |
| { WM_COMMAND, MAKEWORD(ID_START_BUTTON + 99, BN_CLICKED), 0 }, | |
| { WM_COMMAND, MAKEWORD(IDOK, BN_CLICKED), 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_press_nonexistent_buttons_with_command[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_all_common_button_click_with_command }, | |
| { TDN_BUTTON_CLICKED, ID_START_BUTTON, 0, S_FALSE, NULL }, | |
| { TDN_BUTTON_CLICKED, ID_START_BUTTON, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_send_nonexistent_radio_button_click[] = | |
| { | |
| { TDM_CLICK_RADIO_BUTTON, ID_START_RADIO_BUTTON + 99, 0 }, | |
| { TDM_CLICK_BUTTON, IDOK, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_press_nonexistent_radio_button[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_nonexistent_radio_button_click }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_default_verification_unchecked[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_default_verification_checked[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_uncheck_verification[] = | |
| { | |
| { TDM_CLICK_VERIFICATION, FALSE, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_verification_unchecked[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_uncheck_verification }, | |
| { TDN_VERIFICATION_CLICKED, FALSE, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_check_verification[] = | |
| { | |
| { TDM_CLICK_VERIFICATION, TRUE, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_verification_checked[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_check_verification }, | |
| { TDN_VERIFICATION_CLICKED, TRUE, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static TASKDIALOGCONFIG navigated_info = {0}; | |
| static const struct message_info msg_send_navigate[] = | |
| { | |
| { TDM_NAVIGATE_PAGE, 0, (LPARAM)&navigated_info, 0}, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_return_navigated_page[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, NULL }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, msg_send_navigate }, | |
| { TDN_DIALOG_CONSTRUCTED, 0, 0, S_OK, NULL }, | |
| { TDN_RADIO_BUTTON_CLICKED, ID_START_RADIO_BUTTON, 0, S_OK, NULL }, | |
| { TDN_NAVIGATED, 0, 0, S_OK, msg_send_click_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_send_close[] = | |
| { | |
| { WM_CLOSE, 0, 0, 0}, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_handle_wm_close[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_close }, | |
| { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_FALSE, msg_send_close }, | |
| { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_send_close_then_ok[] = | |
| { | |
| { WM_CLOSE, 0, 0, 0}, | |
| { TDM_CLICK_BUTTON, IDOK, 0 }, | |
| { 0 } | |
| }; | |
| static const struct message_info msg_handle_wm_close_without_cancel_button[] = | |
| { | |
| { TDN_CREATED, 0, 0, S_OK, msg_send_close_then_ok }, | |
| { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, | |
| { 0 } | |
| }; | |
| static void init_test_message(UINT message, WPARAM wParam, LPARAM lParam, struct message *msg) | |
| { | |
| msg->message = WM_TD_CALLBACK; | |
| msg->flags = sent|wparam|lparam|id; | |
| msg->wParam = wParam; | |
| msg->lParam = lParam; | |
| msg->id = message; | |
| msg->stage = 0; | |
| } | |
| #define run_test(info, expect_button, expect_radio_button, verification_checked, seq, context) \ | |
| run_test_(info, expect_button, expect_radio_button, verification_checked, seq, context, \ | |
| ARRAY_SIZE(seq) - 1, __FILE__, __LINE__) | |
| static void run_test_(TASKDIALOGCONFIG *info, int expect_button, int expect_radio_button, BOOL verification_checked, | |
| const struct message_info *test_messages, const char *context, int test_messages_len, | |
| const char *file, int line) | |
| { | |
| struct message *msg, *msg_start; | |
| int ret_button = 0; | |
| int ret_radio = 0; | |
| BOOL ret_verification = FALSE; | |
| HRESULT hr; | |
| int i; | |
| /* Allocate messages to test against, plus 2 implicit and 1 empty */ | |
| msg_start = msg = calloc(test_messages_len + 3, sizeof(*msg)); | |
| /* Always needed, thus made implicit */ | |
| init_test_message(TDN_DIALOG_CONSTRUCTED, 0, 0, msg++); | |
| for (i = 0; i < test_messages_len; i++) | |
| init_test_message(test_messages[i].message, test_messages[i].wparam, test_messages[i].lparam, msg++); | |
| /* Always needed, thus made implicit */ | |
| init_test_message(TDN_DESTROYED, 0, 0, msg++); | |
| current_message_info = test_messages; | |
| flush_sequences(sequences, NUM_MSG_SEQUENCES); | |
| hr = pTaskDialogIndirect(info, &ret_button, &ret_radio, &ret_verification); | |
| ok_(file, line)(hr == S_OK, "TaskDialogIndirect() failed, got %#lx.\n", hr); | |
| ok_sequence_(sequences, TASKDIALOG_SEQ_INDEX, msg_start, context, FALSE, file, line); | |
| ok_(file, line)(ret_button == expect_button, | |
| "Wrong button. Expected %d, got %d\n", expect_button, ret_button); | |
| ok_(file, line)(ret_radio == expect_radio_button, | |
| "Wrong radio button. Expected %d, got %d\n", expect_radio_button, ret_radio); | |
| free(msg_start); | |
| } | |
| static const LONG_PTR test_ref_data = 123456; | |
| static HRESULT CALLBACK taskdialog_callback_proc(HWND hwnd, UINT notification, | |
| WPARAM wParam, LPARAM lParam, LONG_PTR ref_data) | |
| { | |
| int msg_pos = sequences[TASKDIALOG_SEQ_INDEX]->count - 1; /* Skip implicit message */ | |
| const struct message_info *msg_send; | |
| struct message msg; | |
| ok(test_ref_data == ref_data, "Unexpected ref data %Iu.\n", ref_data); | |
| init_test_message(notification, (short)wParam, lParam, &msg); | |
| add_message(sequences, TASKDIALOG_SEQ_INDEX, &msg); | |
| if (notification == TDN_DIALOG_CONSTRUCTED || notification == TDN_DESTROYED) /* Skip implicit messages */ | |
| return S_OK; | |
| msg_send = current_message_info[msg_pos].send; | |
| for(; msg_send && msg_send->message; msg_send++) | |
| PostMessageW(hwnd, msg_send->message, msg_send->wparam, msg_send->lparam); | |
| return current_message_info[msg_pos].callback_retval; | |
| } | |
| static void test_invalid_parameters(void) | |
| { | |
| TASKDIALOGCONFIG info = { 0 }; | |
| HRESULT hr; | |
| hr = pTaskDialogIndirect(NULL, NULL, NULL, NULL); | |
| ok(hr == E_INVALIDARG, "Unexpected return value %#lx.\n", hr); | |
| info.cbSize = 0; | |
| hr = pTaskDialogIndirect(&info, NULL, NULL, NULL); | |
| ok(hr == E_INVALIDARG, "Unexpected return value %#lx.\n", hr); | |
| info.cbSize = sizeof(TASKDIALOGCONFIG) - 1; | |
| hr = pTaskDialogIndirect(&info, NULL, NULL, NULL); | |
| ok(hr == E_INVALIDARG, "Unexpected return value %#lx.\n", hr); | |
| info.cbSize = sizeof(TASKDIALOGCONFIG) + 1; | |
| hr = pTaskDialogIndirect(&info, NULL, NULL, NULL); | |
| ok(hr == E_INVALIDARG, "Unexpected return value %#lx.\n", hr); | |
| } | |
| static void test_callback(void) | |
| { | |
| TASKDIALOGCONFIG info = {0}; | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.pfCallback = taskdialog_callback_proc; | |
| info.lpCallbackData = test_ref_data; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_press_ok, "Press VK_RETURN."); | |
| } | |
| static void test_buttons(void) | |
| { | |
| TASKDIALOGCONFIG info = {0}; | |
| static const DWORD command_link_flags[] = {0, TDF_USE_COMMAND_LINKS, TDF_USE_COMMAND_LINKS_NO_ICON}; | |
| TASKDIALOG_BUTTON custom_buttons[TEST_NUM_BUTTONS], radio_buttons[TEST_NUM_RADIO_BUTTONS]; | |
| /* Each button has two digits as title, plus null-terminator */ | |
| WCHAR button_titles[TEST_NUM_BUTTONS * 3], radio_button_titles[TEST_NUM_BUTTONS * 3]; | |
| int i; | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.pfCallback = taskdialog_callback_proc; | |
| info.lpCallbackData = test_ref_data; | |
| /* Init custom buttons */ | |
| for (i = 0; i < TEST_NUM_BUTTONS; i++) | |
| { | |
| WCHAR *text = &button_titles[i * 3]; | |
| wsprintfW(text, L"%02d", i); | |
| custom_buttons[i].pszButtonText = text; | |
| custom_buttons[i].nButtonID = ID_START_BUTTON + i; | |
| } | |
| custom_buttons[TEST_NUM_BUTTONS - 1].nButtonID = -1; | |
| /* Init radio buttons */ | |
| for (i = 0; i < TEST_NUM_RADIO_BUTTONS; i++) | |
| { | |
| WCHAR *text = &radio_button_titles[i * 3]; | |
| wsprintfW(text, L"%02d", i); | |
| radio_buttons[i].pszButtonText = text; | |
| radio_buttons[i].nButtonID = ID_START_RADIO_BUTTON + i; | |
| } | |
| radio_buttons[TEST_NUM_RADIO_BUTTONS - 1].nButtonID = -2; | |
| /* Test nDefaultButton */ | |
| /* Test common buttons with invalid default ID */ | |
| info.nDefaultButton = 0; /* Should default to first created button */ | |
| info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | |
| | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_press_ok, "default button: unset default"); | |
| info.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | |
| | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; | |
| run_test(&info, IDYES, 0, FALSE, msg_return_press_yes, "default button: unset default"); | |
| info.dwCommonButtons = TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; | |
| run_test(&info, IDNO, 0, FALSE, msg_return_press_no, "default button: unset default"); | |
| info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; | |
| run_test(&info, IDRETRY, 0, FALSE, msg_return_press_retry, "default button: unset default"); | |
| info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON; | |
| run_test(&info, IDCANCEL, 0, FALSE, msg_return_press_cancel, "default button: unset default"); | |
| /* Custom buttons could be command links */ | |
| for (i = 0; i < ARRAY_SIZE(command_link_flags); i++) | |
| { | |
| info.dwFlags = command_link_flags[i]; | |
| /* Test with all common and custom buttons and invalid default ID */ | |
| info.nDefaultButton = 0xff; /* Random ID, should also default to first created button */ | |
| info.cButtons = TEST_NUM_BUTTONS; | |
| info.pButtons = custom_buttons; | |
| run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1, | |
| "default button: invalid default, with common buttons - 1"); | |
| info.nDefaultButton = -1; /* Should work despite button ID -1 */ | |
| run_test(&info, -1, 0, FALSE, msg_return_press_custom10, "default button: invalid default, with common buttons - 2"); | |
| info.nDefaultButton = -2; /* Should also default to first created button */ | |
| run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1, | |
| "default button: invalid default, with common buttons - 3"); | |
| /* Test with only custom buttons and invalid default ID */ | |
| info.dwCommonButtons = 0; | |
| run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1, | |
| "default button: invalid default, no common buttons"); | |
| /* Test with common and custom buttons and valid default ID */ | |
| info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | |
| | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; | |
| info.nDefaultButton = IDRETRY; | |
| run_test(&info, IDRETRY, 0, FALSE, msg_return_press_retry, "default button: valid default - 1"); | |
| /* Test with common and custom buttons and valid default ID */ | |
| info.nDefaultButton = ID_START_BUTTON + 3; | |
| run_test(&info, ID_START_BUTTON + 3, 0, FALSE, msg_return_press_custom4, "default button: valid default - 2"); | |
| } | |
| /* Test radio buttons */ | |
| info.nDefaultButton = 0; | |
| info.cButtons = 0; | |
| info.pButtons = 0; | |
| info.dwCommonButtons = TDCBF_OK_BUTTON; | |
| info.cRadioButtons = TEST_NUM_RADIO_BUTTONS; | |
| info.pRadioButtons = radio_buttons; | |
| /* Test default first radio button */ | |
| run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_default_radio_button_1, | |
| "default radio button: default first radio button"); | |
| /* Test default radio button */ | |
| info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1; | |
| run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_default_radio_button_2, | |
| "default radio button: default radio button"); | |
| /* Test default radio button with -2 */ | |
| info.nDefaultRadioButton = -2; | |
| run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_default_radio_button_3, | |
| "default radio button: default radio button with id -2"); | |
| /* Test default radio button after clicking the first, messages still work even radio button is disabled */ | |
| info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1; | |
| run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_first_radio_button, | |
| "default radio button: radio button after clicking"); | |
| /* Test radio button after disabling and clicking the first */ | |
| info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1; | |
| run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_default_radio_button_clicking_disabled, | |
| "default radio button: disable radio button before clicking"); | |
| /* 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 */ | |
| info.nDefaultRadioButton = ID_START_RADIO_BUTTON; | |
| info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; | |
| run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_no_default_radio_button_flag, | |
| "default radio button: no default radio flag"); | |
| /* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is 0. | |
| * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */ | |
| info.nDefaultRadioButton = 0; | |
| info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_no_default_radio_button_id_and_flag, | |
| "default radio button: no default radio id and flag"); | |
| /* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is invalid. | |
| * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */ | |
| info.nDefaultRadioButton = 0xff; | |
| info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_no_default_radio_button_id_and_flag, | |
| "default radio button: no default flag, invalid id"); | |
| info.nDefaultRadioButton = 0; | |
| info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; | |
| run_test(&info, IDOK, -2, FALSE, msg_return_press_negative_id_radio_button, | |
| "radio button: manually click radio button with negative id"); | |
| /* Test sending clicks to non-existent buttons. Notification of non-existent buttons will be sent */ | |
| info.cButtons = TEST_NUM_BUTTONS; | |
| info.pButtons = custom_buttons; | |
| info.cRadioButtons = TEST_NUM_RADIO_BUTTONS; | |
| info.pRadioButtons = radio_buttons; | |
| info.dwCommonButtons = 0; | |
| info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; | |
| run_test(&info, ID_START_BUTTON + 99, 0, FALSE, msg_press_nonexistent_buttons, "sends click to non-existent buttons"); | |
| /* Non-existent button clicks sent by WM_COMMAND won't generate TDN_BUTTON_CLICKED except IDOK. | |
| * And will get the first existent button identifier instead of IDOK */ | |
| run_test(&info, ID_START_BUTTON, 0, FALSE, msg_press_nonexistent_buttons_with_command, | |
| "sends click to non-existent buttons with WM_COMMAND"); | |
| /* Non-existent radio button won't get notifications */ | |
| run_test(&info, IDOK, 0, FALSE, msg_press_nonexistent_radio_button, "sends click to non-existent radio buttons"); | |
| } | |
| static void test_help(void) | |
| { | |
| TASKDIALOGCONFIG info = {0}; | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.pfCallback = taskdialog_callback_proc; | |
| info.lpCallbackData = test_ref_data; | |
| info.dwCommonButtons = TDCBF_OK_BUTTON; | |
| run_test(&info, IDOK, 0, FALSE, msg_got_tdn_help, "send f1"); | |
| } | |
| struct timer_notification_data | |
| { | |
| DWORD last_elapsed_ms; | |
| DWORD num_fired; | |
| }; | |
| static HRESULT CALLBACK taskdialog_callback_proc_timer(HWND hwnd, UINT notification, | |
| WPARAM wParam, LPARAM lParam, LONG_PTR ref_data) | |
| { | |
| struct timer_notification_data *data = (struct timer_notification_data *)ref_data; | |
| if (notification == TDN_TIMER) | |
| { | |
| DWORD elapsed_ms; | |
| int delta; | |
| elapsed_ms = (DWORD)wParam; | |
| if (data->num_fired == 3) | |
| ok(data->last_elapsed_ms > elapsed_ms, "Expected reference time update.\n"); | |
| else | |
| { | |
| delta = elapsed_ms - data->last_elapsed_ms; | |
| ok(delta > 0, "Expected positive time tick difference.\n"); | |
| } | |
| data->last_elapsed_ms = elapsed_ms; | |
| if (data->num_fired == 3) | |
| PostMessageW(hwnd, TDM_CLICK_BUTTON, IDOK, 0); | |
| ++data->num_fired; | |
| return data->num_fired == 3 ? S_FALSE : S_OK; | |
| } | |
| return S_OK; | |
| } | |
| static void test_timer(void) | |
| { | |
| struct timer_notification_data data = { 0 }; | |
| TASKDIALOGCONFIG info = { 0 }; | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.pfCallback = taskdialog_callback_proc_timer; | |
| info.lpCallbackData = (LONG_PTR)&data; | |
| info.dwFlags = TDF_CALLBACK_TIMER; | |
| info.dwCommonButtons = TDCBF_OK_BUTTON; | |
| pTaskDialogIndirect(&info, NULL, NULL, NULL); | |
| } | |
| static HRESULT CALLBACK taskdialog_callback_proc_progress_bar(HWND hwnd, UINT notification, WPARAM wParam, | |
| LPARAM lParam, LONG_PTR ref_data) | |
| { | |
| unsigned long ret; | |
| LONG flags = (LONG)ref_data; | |
| if (notification == TDN_CREATED) | |
| { | |
| /* TDM_SET_PROGRESS_BAR_STATE */ | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_NORMAL, 0); | |
| ok(ret == PBST_NORMAL, "Expect state: %d got state: %lx\n", PBST_NORMAL, ret); | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_PAUSED, 0); | |
| ok(ret == PBST_NORMAL, "Expect state: %d got state: %lx\n", PBST_NORMAL, ret); | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_ERROR, 0); | |
| ok(ret == PBST_PAUSED, "Expect state: %d got state: %lx\n", PBST_PAUSED, ret); | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_STATE, PBST_NORMAL, 0); | |
| ok(ret == PBST_ERROR, "Expect state: %d got state: %lx\n", PBST_ERROR, ret); | |
| /* TDM_SET_PROGRESS_BAR_RANGE */ | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_RANGE, 0, MAKELPARAM(0, 200)); | |
| ok(ret == MAKELONG(0, 100), "Expect range:%lx got:%lx\n", MAKELONG(0, 100), ret); | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_RANGE, 0, MAKELPARAM(0, 200)); | |
| ok(ret == MAKELONG(0, 200), "Expect range:%lx got:%lx\n", MAKELONG(0, 200), ret); | |
| /* TDM_SET_PROGRESS_BAR_POS */ | |
| if (flags & TDF_SHOW_MARQUEE_PROGRESS_BAR) | |
| { | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 1, 0); | |
| ok(ret == 0, "Expect position:%x got:%lx\n", 0, ret); | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 2, 0); | |
| ok(ret == 0, "Expect position:%x got:%lx\n", 0, ret); | |
| } | |
| else | |
| { | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 1, 0); | |
| ok(ret == 0, "Expect position:%x got:%lx\n", 0, ret); | |
| ret = SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, 2, 0); | |
| ok(ret == 1, "Expect position:%x got:%lx\n", 1, ret); | |
| } | |
| SendMessageW(hwnd, TDM_CLICK_BUTTON, IDOK, 0); | |
| } | |
| return S_OK; | |
| } | |
| static void test_progress_bar(void) | |
| { | |
| TASKDIALOGCONFIG info = {0}; | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.dwFlags = TDF_SHOW_PROGRESS_BAR; | |
| info.pfCallback = taskdialog_callback_proc_progress_bar; | |
| info.lpCallbackData = (LONG_PTR)info.dwFlags; | |
| info.dwCommonButtons = TDCBF_OK_BUTTON; | |
| pTaskDialogIndirect(&info, NULL, NULL, NULL); | |
| info.dwFlags = TDF_SHOW_MARQUEE_PROGRESS_BAR; | |
| info.lpCallbackData = (LONG_PTR)info.dwFlags; | |
| pTaskDialogIndirect(&info, NULL, NULL, NULL); | |
| info.dwFlags = TDF_SHOW_PROGRESS_BAR | TDF_SHOW_MARQUEE_PROGRESS_BAR; | |
| info.lpCallbackData = (LONG_PTR)info.dwFlags; | |
| pTaskDialogIndirect(&info, NULL, NULL, NULL); | |
| } | |
| static void test_verification_box(void) | |
| { | |
| TASKDIALOGCONFIG info = {0}; | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.pfCallback = taskdialog_callback_proc; | |
| info.lpCallbackData = test_ref_data; | |
| info.dwCommonButtons = TDCBF_OK_BUTTON; | |
| /* TDF_VERIFICATION_FLAG_CHECKED works even if pszVerificationText is not set */ | |
| run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_unchecked, "default verification box: unchecked"); | |
| info.dwFlags = TDF_VERIFICATION_FLAG_CHECKED; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_checked, "default verification box: checked"); | |
| info.pszVerificationText = L"text"; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_unchecked, "default verification box: unchecked"); | |
| info.dwFlags = TDF_VERIFICATION_FLAG_CHECKED; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_checked, "default verification box: checked"); | |
| run_test(&info, IDOK, 0, FALSE, msg_return_verification_unchecked, | |
| "default verification box: default checked and then unchecked"); | |
| info.dwFlags = 0; | |
| run_test(&info, IDOK, 0, FALSE, msg_return_verification_checked, | |
| "default verification box: default unchecked and then checked"); | |
| } | |
| static void test_navigate_page(void) | |
| { | |
| TASKDIALOGCONFIG info = {0}; | |
| TASKDIALOG_BUTTON radio_buttons[TEST_NUM_RADIO_BUTTONS]; | |
| WCHAR radio_button_titles[TEST_NUM_BUTTONS * 3]; | |
| int i; | |
| /* Init radio buttons */ | |
| for (i = 0; i < TEST_NUM_RADIO_BUTTONS; i++) | |
| { | |
| WCHAR *text = &radio_button_titles[i * 3]; | |
| wsprintfW(text, L"%02d", i); | |
| radio_buttons[i].pszButtonText = text; | |
| radio_buttons[i].nButtonID = ID_START_RADIO_BUTTON + i; | |
| } | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.pfCallback = taskdialog_callback_proc; | |
| info.lpCallbackData = test_ref_data; | |
| info.dwCommonButtons = TDCBF_OK_BUTTON; | |
| info.cRadioButtons = TEST_NUM_RADIO_BUTTONS; | |
| info.pRadioButtons = radio_buttons; | |
| navigated_info = info; | |
| navigated_info.pszVerificationText = L"text"; | |
| navigated_info.dwFlags = TDF_VERIFICATION_FLAG_CHECKED; | |
| run_test(&info, IDOK, ID_START_RADIO_BUTTON, TRUE, msg_return_navigated_page, "navigate page: default"); | |
| /* TDM_NAVIGATE_PAGE doesn't check cbSize. | |
| * And null taskconfig pointer crash applicatioin, thus doesn't check pointer either */ | |
| navigated_info.cbSize = 0; | |
| run_test(&info, IDOK, ID_START_RADIO_BUTTON, TRUE, msg_return_navigated_page, "navigate page: invalid taskconfig cbSize"); | |
| } | |
| static void test_wm_close(void) | |
| { | |
| TASKDIALOGCONFIG info = {0}; | |
| info.cbSize = sizeof(TASKDIALOGCONFIG); | |
| info.pfCallback = taskdialog_callback_proc; | |
| info.lpCallbackData = test_ref_data; | |
| /* WM_CLOSE can end the dialog only when a cancel button is present or dwFlags has TDF_ALLOW_DIALOG_CANCELLATION */ | |
| info.dwCommonButtons = TDCBF_OK_BUTTON; | |
| run_test(&info, IDOK, 0, FALSE, msg_handle_wm_close_without_cancel_button, "send WM_CLOSE without cancel button"); | |
| info.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION; | |
| run_test(&info, IDCANCEL, 0, FALSE, msg_handle_wm_close, "send WM_CLOSE with TDF_ALLOW_DIALOG_CANCELLATION"); | |
| info.dwFlags = 0; | |
| info.dwCommonButtons = TDCBF_CANCEL_BUTTON; | |
| run_test(&info, IDCANCEL, 0, FALSE, msg_handle_wm_close, "send WM_CLOSE with a cancel button"); | |
| } | |
| START_TEST(taskdialog) | |
| { | |
| ULONG_PTR ctx_cookie; | |
| void *ptr_ordinal; | |
| HINSTANCE hinst; | |
| HANDLE hCtx; | |
| if (!load_v6_module(&ctx_cookie, &hCtx)) | |
| return; | |
| /* Check if task dialogs are available */ | |
| hinst = LoadLibraryA("comctl32.dll"); | |
| pTaskDialogIndirect = (void *)GetProcAddress(hinst, "TaskDialogIndirect"); | |
| if (!pTaskDialogIndirect) | |
| { | |
| win_skip("TaskDialogIndirect not exported by name\n"); | |
| unload_v6_module(ctx_cookie, hCtx); | |
| return; | |
| } | |
| pTaskDialog = (void *)GetProcAddress(hinst, "TaskDialog"); | |
| ptr_ordinal = GetProcAddress(hinst, (const char *)344); | |
| ok(pTaskDialog == ptr_ordinal, "got wrong pointer for ordinal 344, %p expected %p\n", | |
| ptr_ordinal, pTaskDialog); | |
| ptr_ordinal = GetProcAddress(hinst, (const char *)345); | |
| ok(pTaskDialogIndirect == ptr_ordinal, "got wrong pointer for ordinal 345, %p expected %p\n", | |
| ptr_ordinal, pTaskDialogIndirect); | |
| init_msg_sequences(sequences, NUM_MSG_SEQUENCES); | |
| test_invalid_parameters(); | |
| test_callback(); | |
| test_buttons(); | |
| test_help(); | |
| test_timer(); | |
| test_progress_bar(); | |
| test_verification_box(); | |
| test_navigate_page(); | |
| test_wm_close(); | |
| unload_v6_module(ctx_cookie, hCtx); | |
| } |