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