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 #define WM_TD_CALLBACK (WM_APP) /* Custom dummy message to wrap callback notifications */ 33 34 #define NUM_MSG_SEQUENCES 1 35 #define TASKDIALOG_SEQ_INDEX 0 36 37 #define TEST_NUM_BUTTONS 10 /* Number of custom buttons to test with */ 38 39 #define ID_START 20 /* Lower IDs might be used by the system */ 40 #define ID_START_BUTTON (ID_START + 0) 41 42 static HRESULT (WINAPI *pTaskDialogIndirect)(const TASKDIALOGCONFIG *, int *, int *, BOOL *); 43 static HRESULT (WINAPI *pTaskDialog)(HWND, HINSTANCE, const WCHAR *, const WCHAR *, const WCHAR *, 44 TASKDIALOG_COMMON_BUTTON_FLAGS, const WCHAR *, int *); 45 46 static struct msg_sequence *sequences[NUM_MSG_SEQUENCES]; 47 48 struct message_info 49 { 50 UINT message; 51 WPARAM wparam; 52 LPARAM lparam; 53 54 HRESULT callback_retval; 55 const struct message_info *send; /* Message to send to trigger the next callback message */ 56 }; 57 58 static const struct message_info *current_message_info; 59 60 /* Messages to send */ 61 static const struct message_info msg_send_return[] = 62 { 63 { WM_KEYDOWN, VK_RETURN, 0 }, 64 { 0 } 65 }; 66 67 /* Messages to test against */ 68 static const struct message_info msg_return_press_ok[] = 69 { 70 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 71 { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, 72 { 0 } 73 }; 74 75 static const struct message_info msg_return_press_yes[] = 76 { 77 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 78 { TDN_BUTTON_CLICKED, IDYES, 0, S_OK, NULL }, 79 { 0 } 80 }; 81 82 static const struct message_info msg_return_press_no[] = 83 { 84 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 85 { TDN_BUTTON_CLICKED, IDNO, 0, S_OK, NULL }, 86 { 0 } 87 }; 88 89 static const struct message_info msg_return_press_cancel[] = 90 { 91 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 92 { TDN_BUTTON_CLICKED, IDCANCEL, 0, S_OK, NULL }, 93 { 0 } 94 }; 95 96 static const struct message_info msg_return_press_retry[] = 97 { 98 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 99 { TDN_BUTTON_CLICKED, IDRETRY, 0, S_OK, NULL }, 100 { 0 } 101 }; 102 103 static const struct message_info msg_return_press_custom1[] = 104 { 105 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 106 { TDN_BUTTON_CLICKED, ID_START_BUTTON, 0, S_OK, NULL }, 107 { 0 } 108 }; 109 110 static const struct message_info msg_return_press_custom4[] = 111 { 112 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 113 { TDN_BUTTON_CLICKED, ID_START_BUTTON + 3, 0, S_OK, NULL }, 114 { 0 } 115 }; 116 117 static const struct message_info msg_return_press_custom10[] = 118 { 119 { TDN_CREATED, 0, 0, S_OK, msg_send_return }, 120 { TDN_BUTTON_CLICKED, -1, 0, S_OK, NULL }, 121 { 0 } 122 }; 123 124 static void init_test_message(UINT message, WPARAM wParam, LPARAM lParam, struct message *msg) 125 { 126 msg->message = WM_TD_CALLBACK; 127 msg->flags = sent|wparam|lparam|id; 128 msg->wParam = wParam; 129 msg->lParam = lParam; 130 msg->id = message; 131 msg->stage = 0; 132 } 133 134 #define run_test(info, expect_button, seq, context) \ 135 run_test_(info, expect_button, seq, context, \ 136 sizeof(seq)/sizeof(seq[0]) - 1, __FILE__, __LINE__) 137 138 static void run_test_(TASKDIALOGCONFIG *info, int expect_button, const struct message_info *test_messages, 139 const char *context, int test_messages_len, const char *file, int line) 140 { 141 struct message *msg, *msg_start; 142 int ret_button = 0; 143 int ret_radio = 0; 144 HRESULT hr; 145 int i; 146 147 /* Allocate messages to test against, plus 2 implicit and 1 empty */ 148 msg_start = msg = heap_alloc_zero(sizeof(*msg) * (test_messages_len + 3)); 149 150 /* Always needed, thus made implicit */ 151 init_test_message(TDN_DIALOG_CONSTRUCTED, 0, 0, msg++); 152 for (i = 0; i < test_messages_len; i++) 153 init_test_message(test_messages[i].message, test_messages[i].wparam, test_messages[i].lparam, msg++); 154 /* Always needed, thus made implicit */ 155 init_test_message(TDN_DESTROYED, 0, 0, msg++); 156 157 current_message_info = test_messages; 158 flush_sequences(sequences, NUM_MSG_SEQUENCES); 159 160 hr = pTaskDialogIndirect(info, &ret_button, &ret_radio, NULL); 161 ok_(file, line)(hr == S_OK, "TaskDialogIndirect() failed, got %#x.\n", hr); 162 163 ok_sequence_(sequences, TASKDIALOG_SEQ_INDEX, msg_start, context, FALSE, file, line); 164 ok_(file, line)(ret_button == expect_button, 165 "Wrong button. Expected %d, got %d\n", expect_button, ret_button); 166 167 heap_free(msg_start); 168 } 169 170 static const LONG_PTR test_ref_data = 123456; 171 172 static HRESULT CALLBACK taskdialog_callback_proc(HWND hwnd, UINT notification, 173 WPARAM wParam, LPARAM lParam, LONG_PTR ref_data) 174 { 175 int msg_pos = sequences[TASKDIALOG_SEQ_INDEX]->count - 1; /* Skip implicit message */ 176 const struct message_info *msg_send; 177 struct message msg; 178 179 ok(test_ref_data == ref_data, "Unexpected ref data %lu.\n", ref_data); 180 181 init_test_message(notification, (short)wParam, lParam, &msg); 182 add_message(sequences, TASKDIALOG_SEQ_INDEX, &msg); 183 184 if (notification == TDN_DIALOG_CONSTRUCTED || notification == TDN_DESTROYED) /* Skip implicit messages */ 185 return S_OK; 186 187 msg_send = current_message_info[msg_pos].send; 188 for(; msg_send && msg_send->message; msg_send++) 189 PostMessageW(hwnd, msg_send->message, msg_send->wparam, msg_send->lparam); 190 191 return current_message_info[msg_pos].callback_retval; 192 } 193 194 static void test_invalid_parameters(void) 195 { 196 TASKDIALOGCONFIG info = { 0 }; 197 HRESULT hr; 198 199 hr = pTaskDialogIndirect(NULL, NULL, NULL, NULL); 200 ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr); 201 202 info.cbSize = 0; 203 hr = pTaskDialogIndirect(&info, NULL, NULL, NULL); 204 ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr); 205 206 info.cbSize = sizeof(TASKDIALOGCONFIG) - 1; 207 hr = pTaskDialogIndirect(&info, NULL, NULL, NULL); 208 ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr); 209 210 info.cbSize = sizeof(TASKDIALOGCONFIG) + 1; 211 hr = pTaskDialogIndirect(&info, NULL, NULL, NULL); 212 ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr); 213 } 214 215 static void test_callback(void) 216 { 217 TASKDIALOGCONFIG info = {0}; 218 219 info.cbSize = sizeof(TASKDIALOGCONFIG); 220 info.pfCallback = taskdialog_callback_proc; 221 info.lpCallbackData = test_ref_data; 222 223 run_test(&info, IDOK, msg_return_press_ok, "Press VK_RETURN."); 224 } 225 226 static void test_buttons(void) 227 { 228 TASKDIALOGCONFIG info = {0}; 229 230 TASKDIALOG_BUTTON custom_buttons[TEST_NUM_BUTTONS]; 231 const WCHAR button_format[] = {'%','0','2','d',0}; 232 WCHAR button_titles[TEST_NUM_BUTTONS * 3]; /* Each button has two digits as title, plus null-terminator */ 233 int i; 234 235 info.cbSize = sizeof(TASKDIALOGCONFIG); 236 info.pfCallback = taskdialog_callback_proc; 237 info.lpCallbackData = test_ref_data; 238 239 /* Init custom buttons */ 240 for (i = 0; i < TEST_NUM_BUTTONS; i++) 241 { 242 WCHAR *text = &button_titles[i * 3]; 243 wsprintfW(text, button_format, i); 244 245 custom_buttons[i].pszButtonText = text; 246 custom_buttons[i].nButtonID = ID_START_BUTTON + i; 247 } 248 custom_buttons[TEST_NUM_BUTTONS - 1].nButtonID = -1; 249 250 /* Test nDefaultButton */ 251 252 /* Test common buttons with invalid default ID */ 253 info.nDefaultButton = 0; /* Should default to first created button */ 254 info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON 255 | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; 256 run_test(&info, IDOK, msg_return_press_ok, "default button: unset default"); 257 info.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON 258 | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; 259 run_test(&info, IDYES, msg_return_press_yes, "default button: unset default"); 260 info.dwCommonButtons = TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; 261 run_test(&info, IDNO, msg_return_press_no, "default button: unset default"); 262 info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; 263 run_test(&info, IDRETRY, msg_return_press_retry, "default button: unset default"); 264 info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON; 265 run_test(&info, IDCANCEL, msg_return_press_cancel, "default button: unset default"); 266 267 /* Test with all common and custom buttons and invalid default ID */ 268 info.nDefaultButton = 0xff; /* Random ID, should also default to first created button */ 269 info.cButtons = TEST_NUM_BUTTONS; 270 info.pButtons = custom_buttons; 271 run_test(&info, ID_START_BUTTON, msg_return_press_custom1, "default button: invalid default, with common buttons - 1"); 272 273 info.nDefaultButton = -1; /* Should work despite button ID -1 */ 274 run_test(&info, -1, msg_return_press_custom10, "default button: invalid default, with common buttons - 2"); 275 276 info.nDefaultButton = -2; /* Should also default to first created button */ 277 run_test(&info, ID_START_BUTTON, msg_return_press_custom1, "default button: invalid default, with common buttons - 3"); 278 279 /* Test with only custom buttons and invalid default ID */ 280 info.dwCommonButtons = 0; 281 run_test(&info, ID_START_BUTTON, msg_return_press_custom1, "default button: invalid default, no common buttons"); 282 283 /* Test with common and custom buttons and valid default ID */ 284 info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON 285 | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; 286 info.nDefaultButton = IDRETRY; 287 run_test(&info, IDRETRY, msg_return_press_retry, "default button: valid default - 1"); 288 289 /* Test with common and custom buttons and valid default ID */ 290 info.nDefaultButton = ID_START_BUTTON + 3; 291 run_test(&info, ID_START_BUTTON + 3, msg_return_press_custom4, "default button: valid default - 2"); 292 } 293 294 START_TEST(taskdialog) 295 { 296 ULONG_PTR ctx_cookie; 297 void *ptr_ordinal; 298 HINSTANCE hinst; 299 HANDLE hCtx; 300 301 if (!load_v6_module(&ctx_cookie, &hCtx)) 302 return; 303 304 /* Check if task dialogs are available */ 305 hinst = LoadLibraryA("comctl32.dll"); 306 307 pTaskDialogIndirect = (void *)GetProcAddress(hinst, "TaskDialogIndirect"); 308 if (!pTaskDialogIndirect) 309 { 310 win_skip("TaskDialogIndirect not exported by name\n"); 311 unload_v6_module(ctx_cookie, hCtx); 312 return; 313 } 314 315 pTaskDialog = (void *)GetProcAddress(hinst, "TaskDialog"); 316 317 ptr_ordinal = GetProcAddress(hinst, (const char *)344); 318 ok(pTaskDialog == ptr_ordinal, "got wrong pointer for ordinal 344, %p expected %p\n", 319 ptr_ordinal, pTaskDialog); 320 321 ptr_ordinal = GetProcAddress(hinst, (const char *)345); 322 ok(pTaskDialogIndirect == ptr_ordinal, "got wrong pointer for ordinal 345, %p expected %p\n", 323 ptr_ordinal, pTaskDialogIndirect); 324 325 init_msg_sequences(sequences, NUM_MSG_SEQUENCES); 326 327 test_invalid_parameters(); 328 test_callback(); 329 test_buttons(); 330 331 unload_v6_module(ctx_cookie, hCtx); 332 } 333