418 lines
14 KiB
C
418 lines
14 KiB
C
/* 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/heap.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 ID_START 20 /* Lower IDs might be used by the system */
|
|
#define ID_START_BUTTON (ID_START + 0)
|
|
|
|
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 }
|
|
};
|
|
|
|
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, seq, context) \
|
|
run_test_(info, expect_button, seq, context, \
|
|
sizeof(seq)/sizeof(seq[0]) - 1, __FILE__, __LINE__)
|
|
|
|
static void run_test_(TASKDIALOGCONFIG *info, int expect_button, 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;
|
|
HRESULT hr;
|
|
int i;
|
|
|
|
/* Allocate messages to test against, plus 2 implicit and 1 empty */
|
|
msg_start = msg = heap_alloc_zero(sizeof(*msg) * (test_messages_len + 3));
|
|
|
|
/* 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, NULL);
|
|
ok_(file, line)(hr == S_OK, "TaskDialogIndirect() failed, got %#x.\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);
|
|
|
|
heap_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 %lu.\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 %#x.\n", hr);
|
|
|
|
info.cbSize = 0;
|
|
hr = pTaskDialogIndirect(&info, NULL, NULL, NULL);
|
|
ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr);
|
|
|
|
info.cbSize = sizeof(TASKDIALOGCONFIG) - 1;
|
|
hr = pTaskDialogIndirect(&info, NULL, NULL, NULL);
|
|
ok(hr == E_INVALIDARG, "Unexpected return value %#x.\n", hr);
|
|
|
|
info.cbSize = sizeof(TASKDIALOGCONFIG) + 1;
|
|
hr = pTaskDialogIndirect(&info, NULL, NULL, NULL);
|
|
ok(hr == E_INVALIDARG, "Unexpected return value %#x.\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, msg_return_press_ok, "Press VK_RETURN.");
|
|
}
|
|
|
|
static void test_buttons(void)
|
|
{
|
|
TASKDIALOGCONFIG info = {0};
|
|
|
|
TASKDIALOG_BUTTON custom_buttons[TEST_NUM_BUTTONS];
|
|
const WCHAR button_format[] = {'%','0','2','d',0};
|
|
WCHAR button_titles[TEST_NUM_BUTTONS * 3]; /* Each button has two digits as title, plus null-terminator */
|
|
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, button_format, i);
|
|
|
|
custom_buttons[i].pszButtonText = text;
|
|
custom_buttons[i].nButtonID = ID_START_BUTTON + i;
|
|
}
|
|
custom_buttons[TEST_NUM_BUTTONS - 1].nButtonID = -1;
|
|
|
|
/* 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, 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, 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, msg_return_press_no, "default button: unset default");
|
|
info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON;
|
|
run_test(&info, IDRETRY, msg_return_press_retry, "default button: unset default");
|
|
info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON;
|
|
run_test(&info, IDCANCEL, msg_return_press_cancel, "default button: unset default");
|
|
|
|
/* 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, 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, 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, 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, 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, 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, msg_return_press_custom4, "default button: valid default - 2");
|
|
}
|
|
|
|
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, 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);
|
|
}
|
|
|
|
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();
|
|
|
|
unload_v6_module(ctx_cookie, hCtx);
|
|
}
|