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