[Linux] Test text input (flutter/engine#34186)

This PR finishes what #33661 and #33111 started - most of `FlTextInputPlugin` is now covered by tests.
This commit is contained in:
J-P Nurmi 2022-06-21 19:03:30 +02:00 committed by GitHub
parent 9b4d1ccd49
commit b7bfa342dd
7 changed files with 1103 additions and 74 deletions

View File

@ -226,6 +226,7 @@ executable("flutter_linux_unittests") {
"testing/mock_binary_messenger_response_handle.cc",
"testing/mock_engine.cc",
"testing/mock_epoxy.cc",
"testing/mock_im_context.cc",
"testing/mock_plugin_registrar.cc",
"testing/mock_renderer.cc",
"testing/mock_settings.cc",

View File

@ -80,14 +80,8 @@ struct FlTextInputPluginPrivate {
// Input method.
GtkIMContext* im_context;
// IM filter.
FlTextInputPluginImFilter im_filter;
flutter::TextInputModel* text_model;
// The owning native window.
GdkWindow* window;
// A 4x4 matrix that maps from `EditableText` local coordinates to the
// coordinate system of `PipelineOwner.rootNode`.
double editabletext_transform[4][4];
@ -216,8 +210,8 @@ static void update_editing_state_with_delta(FlTextInputPlugin* self,
g_autoptr(FlValue) deltas = fl_value_new_list();
fl_value_append(deltas, deltaValue);
FlValue* value = fl_value_new_map();
fl_value_set_string_take(value, "deltas", deltas);
g_autoptr(FlValue) value = fl_value_new_map();
fl_value_set_string(value, "deltas", deltas);
fl_value_append(args, value);
@ -258,9 +252,6 @@ static void im_preedit_start_cb(FlTextInputPlugin* self) {
FlTextInputPluginPrivate* priv = static_cast<FlTextInputPluginPrivate*>(
fl_text_input_plugin_get_instance_private(self));
priv->text_model->BeginComposing();
// Set the native window used for system input method windows.
gtk_im_context_set_client_window(priv->im_context, priv->window);
}
// Signal handler for GtkIMContext::preedit-changed
@ -418,9 +409,6 @@ static FlMethodResponse* show(FlTextInputPlugin* self) {
return hide(self);
}
// Set the native window used for system input method windows.
gtk_im_context_set_client_window(priv->im_context, priv->window);
gtk_im_context_focus_in(priv->im_context);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
@ -599,7 +587,6 @@ static void fl_text_input_plugin_dispose(GObject* object) {
delete priv->text_model;
priv->text_model = nullptr;
}
priv->window = nullptr;
G_OBJECT_CLASS(fl_text_input_plugin_parent_class)->dispose(object);
}
@ -617,7 +604,8 @@ static gboolean fl_text_input_plugin_filter_keypress_default(
return FALSE;
}
if (priv->im_filter(priv->im_context, event->origin)) {
GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event->origin);
if (gtk_im_context_filter_keypress(priv->im_context, key_event)) {
return TRUE;
}
@ -698,12 +686,20 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
fl_text_input_plugin_get_instance_private(self));
priv->client_id = kClientIdUnset;
priv->im_context = gtk_im_multicontext_new();
priv->input_type = FL_TEXT_INPUT_TYPE_TEXT;
priv->text_model = new flutter::TextInputModel();
}
static void init_im_context(FlTextInputPlugin* self, GtkIMContext* im_context) {
FlTextInputPluginPrivate* priv = static_cast<FlTextInputPluginPrivate*>(
fl_text_input_plugin_get_instance_private(self));
priv->im_context = GTK_IM_CONTEXT(g_object_ref(im_context));
// On Wayland, this call sets up the input method so it can be enabled
// immediately when required. Without it, on-screen keyboard's don't come up
// the first time a text field is focused.
gtk_im_context_focus_out(priv->im_context);
priv->input_type = FL_TEXT_INPUT_TYPE_TEXT;
g_signal_connect_object(priv->im_context, "preedit-start",
G_CALLBACK(im_preedit_start_cb), self,
G_CONNECT_SWAPPED);
@ -721,15 +717,12 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
g_signal_connect_object(priv->im_context, "delete-surrounding",
G_CALLBACK(im_delete_surrounding_cb), self,
G_CONNECT_SWAPPED);
priv->text_model = new flutter::TextInputModel();
}
FlTextInputPlugin* fl_text_input_plugin_new(
FlBinaryMessenger* messenger,
GdkWindow* window,
FlTextInputPluginImFilter im_filter) {
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
GtkIMContext* im_context) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
g_return_val_if_fail(im_filter != nullptr, nullptr);
g_return_val_if_fail(GTK_IS_IM_CONTEXT(im_context), nullptr);
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(
g_object_new(fl_text_input_plugin_get_type(), nullptr));
@ -741,8 +734,8 @@ FlTextInputPlugin* fl_text_input_plugin_new(
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(priv->channel, method_call_cb, self,
nullptr);
priv->window = window;
priv->im_filter = im_filter;
init_im_context(self, im_context);
return self;
}

View File

@ -10,19 +10,6 @@
#include "flutter/shell/platform/linux/fl_key_event.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
/**
* FlTextInputPluginImFilter:
* @event: the pointer to the GdkEventKey.
*
* The signature for a callback with which a #FlTextInputPlugin allow an input
* method to internally handle key press and release events.
*
* The #gdk_event is an opaque pointer. It will be GdkEvent* in actual
* applications, or a dummy pointer in unit tests.
**/
typedef gboolean (*FlTextInputPluginImFilter)(GtkIMContext* im_context,
gpointer gdk_event);
G_BEGIN_DECLS
G_DECLARE_DERIVABLE_TYPE(FlTextInputPlugin,
@ -50,20 +37,15 @@ struct _FlTextInputPluginClass {
/**
* fl_text_input_plugin_new:
* @messenger: an #FlBinaryMessenger.
* @window: the #GdkWindow with which the text input plugin is associated.
* @im_filter: a function used to allow an input method to internally handle
* key press and release events. Typically a wrap of
* #gtk_im_context_filter_keypress. Must not be nullptr.
* @im_context: (allow-none): a #GtkIMContext.
*
* Creates a new plugin that implements SystemChannels.textInput from the
* Flutter services library.
*
* Returns: a new #FlTextInputPlugin.
*/
FlTextInputPlugin* fl_text_input_plugin_new(
FlBinaryMessenger* messenger,
GdkWindow* window,
FlTextInputPluginImFilter im_filter);
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
GtkIMContext* im_context);
/**
* fl_text_input_plugin_filter_keypress

View File

@ -10,6 +10,7 @@
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h"
#include "flutter/shell/platform/linux/testing/mock_binary_messenger_response_handle.h"
#include "flutter/shell/platform/linux/testing/mock_im_context.h"
#include "flutter/testing/testing.h"
#include "gmock/gmock.h"
@ -32,6 +33,66 @@ MATCHER_P(SuccessResponse, result, "") {
return false;
}
MATCHER_P(FlValueEq, value, "equal to " + ::testing::PrintToString(value)) {
return fl_value_equal(arg, value);
}
class MethodCallMatcher {
public:
using is_gtest_matcher = void;
explicit MethodCallMatcher(::testing::Matcher<std::string> name,
::testing::Matcher<FlValue*> args)
: name_(name), args_(args) {}
bool MatchAndExplain(GBytes* method_call,
::testing::MatchResultListener* result_listener) const {
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GError) error = nullptr;
g_autofree gchar* name = nullptr;
g_autoptr(FlValue) args = nullptr;
gboolean result = fl_method_codec_decode_method_call(
FL_METHOD_CODEC(codec), method_call, &name, &args, &error);
if (!result) {
*result_listener << ::testing::PrintToString(error->message);
return false;
}
if (!name_.MatchAndExplain(name, result_listener)) {
*result_listener << " where the name doesn't match: \"" << name << "\"";
return false;
}
if (!args_.MatchAndExplain(args, result_listener)) {
*result_listener << " where the args don't match: "
<< ::testing::PrintToString(args);
return false;
}
return true;
}
void DescribeTo(std::ostream* os) const {
*os << "method name ";
name_.DescribeTo(os);
*os << " and args ";
args_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const {
*os << "method name ";
name_.DescribeNegationTo(os);
*os << " or args ";
args_.DescribeNegationTo(os);
}
private:
::testing::Matcher<std::string> name_;
::testing::Matcher<FlValue*> args_;
};
::testing::Matcher<GBytes*> MethodCall(std::string name,
::testing::Matcher<FlValue*> args) {
return MethodCallMatcher(::testing::StrEq(name), args);
}
static FlValue* build_map(std::map<const gchar*, FlValue*> args) {
FlValue* value = fl_value_new_map();
for (auto it = args.begin(); it != args.end(); ++it) {
@ -48,29 +109,641 @@ static FlValue* build_list(std::vector<FlValue*> args) {
return value;
}
TEST(FlTextInputPluginTest, SetClient) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
auto filter =
+[](GtkIMContext* im_context, gpointer gdk_event) { return false; };
struct InputConfig {
int64_t client_id = -1;
const gchar* input_type = "TextInputType.text";
const gchar* input_action = "TextInputAction.none";
gboolean enable_delta_model = false;
};
fl_text_input_plugin_new(messenger, nullptr,
FlTextInputPluginImFilter(filter));
EXPECT_TRUE(messenger.HasMessageHandler("flutter/textinput"));
g_autoptr(FlValue) args = build_list({
fl_value_new_int(1), // client id
static FlValue* build_input_config(InputConfig config) {
return build_list({
fl_value_new_int(config.client_id),
build_map({
{"inputAction", fl_value_new_string("")},
{"inputAction", fl_value_new_string(config.input_action)},
{"inputType", build_map({
{"name", fl_value_new_string("TextInputType.text")},
{"name", fl_value_new_string(config.input_type)},
})},
{"enableDeltaModel", fl_value_new_bool(false)},
{"enableDeltaModel", fl_value_new_bool(config.enable_delta_model)},
}),
});
}
struct EditingState {
const gchar* text = "";
int selection_base = -1;
int selection_extent = -1;
int composing_base = -1;
int composing_extent = -1;
};
static FlValue* build_editing_state(EditingState state) {
return build_map({
{"text", fl_value_new_string(state.text)},
{"selectionBase", fl_value_new_int(state.selection_base)},
{"selectionExtent", fl_value_new_int(state.selection_extent)},
{"selectionAffinity", fl_value_new_string("TextAffinity.downstream")},
{"selectionIsDirectional", fl_value_new_bool(false)},
{"composingBase", fl_value_new_int(state.composing_base)},
{"composingExtent", fl_value_new_int(state.composing_extent)},
});
}
struct EditingDelta {
const gchar* old_text = "";
const gchar* delta_text = "";
int delta_start = -1;
int delta_end = -1;
int selection_base = -1;
int selection_extent = -1;
int composing_base = -1;
int composing_extent = -1;
};
static FlValue* build_editing_delta(EditingDelta delta) {
return build_map({
{"oldText", fl_value_new_string(delta.old_text)},
{"deltaText", fl_value_new_string(delta.delta_text)},
{"deltaStart", fl_value_new_int(delta.delta_start)},
{"deltaEnd", fl_value_new_int(delta.delta_end)},
{"selectionBase", fl_value_new_int(delta.selection_base)},
{"selectionExtent", fl_value_new_int(delta.selection_extent)},
{"selectionAffinity", fl_value_new_string("TextAffinity.downstream")},
{"selectionIsDirectional", fl_value_new_bool(false)},
{"composingBase", fl_value_new_int(delta.composing_base)},
{"composingExtent", fl_value_new_int(delta.composing_extent)},
});
}
static void send_key_event(FlTextInputPlugin* plugin,
gint keyval,
gint state = 0) {
GdkEvent* gdk_event = gdk_event_new(GDK_KEY_PRESS);
gdk_event->key.keyval = keyval;
gdk_event->key.state = state;
FlKeyEvent* key_event = fl_key_event_new_from_gdk_event(gdk_event);
fl_text_input_plugin_filter_keypress(plugin, key_event);
fl_key_event_dispose(key_event);
}
TEST(FlTextInputPluginTest, MessageHandler) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
EXPECT_TRUE(messenger.HasMessageHandler("flutter/textinput"));
}
TEST(FlTextInputPluginTest, SetClient) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
g_autoptr(FlValue) args = build_input_config({.client_id = 1});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", message);
}
TEST(FlTextInputPluginTest, Show) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
EXPECT_CALL(context,
gtk_im_context_focus_in(::testing::Eq<GtkIMContext*>(context)));
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.show", nullptr, nullptr);
messenger.ReceiveMessage("flutter/textinput", message);
}
TEST(FlTextInputPluginTest, Hide) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
EXPECT_CALL(context,
gtk_im_context_focus_out(::testing::Eq<GtkIMContext*>(context)));
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.hide", nullptr, nullptr);
messenger.ReceiveMessage("flutter/textinput", message);
}
TEST(FlTextInputPluginTest, ClearClient) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.clearClient", nullptr, nullptr);
messenger.ReceiveMessage("flutter/textinput", message);
}
TEST(FlTextInputPluginTest, PerformAction) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
// set input config
g_autoptr(FlValue) config = build_input_config({
.client_id = 1,
.input_type = "TextInputType.multiline",
.input_action = "TextInputAction.newline",
});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_client);
// set editing state
g_autoptr(FlValue) state = build_editing_state({
.text = "Flutter",
.selection_base = 7,
.selection_extent = 7,
});
g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr);
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_state);
// update editing state
g_autoptr(FlValue) new_state = build_list({
fl_value_new_int(1), // client_id
build_editing_state({
.text = "Flutter\n",
.selection_base = 8,
.selection_extent = 8,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(new_state)),
::testing::_, ::testing::_, ::testing::_));
// perform action
g_autoptr(FlValue) action = build_list({
fl_value_new_int(1), // client_id
fl_value_new_string("TextInputAction.newline"),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.performAction",
FlValueEq(action)),
::testing::_, ::testing::_, ::testing::_));
send_key_event(plugin, GDK_KEY_Return);
}
TEST(FlTextInputPluginTest, MoveCursor) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
// set input config
g_autoptr(FlValue) config = build_input_config({.client_id = 1});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_client);
// set editing state
g_autoptr(FlValue) state = build_editing_state({
.text = "Flutter",
.selection_base = 4,
.selection_extent = 4,
});
g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr);
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_state);
// move cursor to beginning
g_autoptr(FlValue) beginning = build_list({
fl_value_new_int(1), // client_id
build_editing_state({
.text = "Flutter",
.selection_base = 0,
.selection_extent = 0,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(beginning)),
::testing::_, ::testing::_, ::testing::_));
send_key_event(plugin, GDK_KEY_Home);
// move cursor to end
g_autoptr(FlValue) end = build_list({
fl_value_new_int(1), // client_id
build_editing_state({
.text = "Flutter",
.selection_base = 7,
.selection_extent = 7,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(end)),
::testing::_, ::testing::_, ::testing::_));
send_key_event(plugin, GDK_KEY_End);
}
TEST(FlTextInputPluginTest, Select) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
// set input config
g_autoptr(FlValue) config = build_input_config({.client_id = 1});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_client);
// set editing state
g_autoptr(FlValue) state = build_editing_state({
.text = "Flutter",
.selection_base = 4,
.selection_extent = 4,
});
g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr);
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_state);
// select to end
g_autoptr(FlValue) select_to_end = build_list({
fl_value_new_int(1), // client_id
build_editing_state({
.text = "Flutter",
.selection_base = 4,
.selection_extent = 7,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(select_to_end)),
::testing::_, ::testing::_, ::testing::_));
send_key_event(plugin, GDK_KEY_End, GDK_SHIFT_MASK);
// select to beginning
g_autoptr(FlValue) select_to_beginning = build_list({
fl_value_new_int(1), // client_id
build_editing_state({
.text = "Flutter",
.selection_base = 4,
.selection_extent = 0,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(select_to_beginning)),
::testing::_, ::testing::_, ::testing::_));
send_key_event(plugin, GDK_KEY_Home, GDK_SHIFT_MASK);
}
TEST(FlTextInputPluginTest, Composing) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
g_signal_emit_by_name(context, "preedit-start", nullptr);
// update
EXPECT_CALL(context,
gtk_im_context_get_preedit_string(
::testing::Eq<GtkIMContext*>(context),
::testing::A<gchar**>(), ::testing::_, ::testing::A<gint*>()))
.WillOnce(
::testing::DoAll(::testing::SetArgPointee<1>(g_strdup("Flutter")),
::testing::SetArgPointee<3>(0)));
g_autoptr(FlValue) state = build_list({
fl_value_new_int(-1), // client_id
build_editing_state({
.text = "Flutter",
.selection_base = 0,
.selection_extent = 0,
.composing_base = 0,
.composing_extent = 7,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(state)),
::testing::_, ::testing::_, ::testing::_));
g_signal_emit_by_name(context, "preedit-changed", nullptr);
// commit
g_autoptr(FlValue) commit = build_list({
fl_value_new_int(-1), // client_id
build_editing_state({
.text = "engine",
.selection_base = 6,
.selection_extent = 6,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(commit)),
::testing::_, ::testing::_, ::testing::_));
g_signal_emit_by_name(context, "commit", "engine", nullptr);
// end
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
::testing::_),
::testing::_, ::testing::_, ::testing::_));
g_signal_emit_by_name(context, "preedit-end", nullptr);
}
TEST(FlTextInputPluginTest, SurroundingText) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
// set input config
g_autoptr(FlValue) config = build_input_config({.client_id = 1});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", config, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_client);
// set editing state
g_autoptr(FlValue) state = build_editing_state({
.text = "Flutter",
.selection_base = 3,
.selection_extent = 3,
});
g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr);
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_state);
// retrieve
EXPECT_CALL(context, gtk_im_context_set_surrounding(
::testing::Eq<GtkIMContext*>(context),
::testing::StrEq("Flutter"), 7, 3));
gboolean retrieved = false;
g_signal_emit_by_name(context, "retrieve-surrounding", &retrieved, nullptr);
EXPECT_TRUE(retrieved);
// delete
g_autoptr(FlValue) update = build_list({
fl_value_new_int(1), // client_id
build_editing_state({
.text = "Flutr",
.selection_base = 3,
.selection_extent = 3,
}),
});
EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingState",
FlValueEq(update)),
::testing::_, ::testing::_, ::testing::_));
gboolean deleted = false;
g_signal_emit_by_name(context, "delete-surrounding", 1, 2, &deleted, nullptr);
EXPECT_TRUE(deleted);
}
TEST(FlTextInputPluginTest, SetMarkedTextRect) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
g_signal_emit_by_name(context, "preedit-start", nullptr);
// set editable size and transform
g_autoptr(FlValue) size_and_transform = build_map({
{
"transform",
build_list({
fl_value_new_float(1),
fl_value_new_float(2),
fl_value_new_float(3),
fl_value_new_float(4),
fl_value_new_float(5),
fl_value_new_float(6),
fl_value_new_float(7),
fl_value_new_float(8),
fl_value_new_float(9),
fl_value_new_float(10),
fl_value_new_float(11),
fl_value_new_float(12),
fl_value_new_float(13),
fl_value_new_float(14),
fl_value_new_float(15),
fl_value_new_float(16),
}),
},
});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_editable_size_and_transform =
fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setEditableSizeAndTransform",
size_and_transform, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput",
set_editable_size_and_transform);
// set marked text rect
g_autoptr(FlValue) rect = build_map({
{"x", fl_value_new_float(1)},
{"y", fl_value_new_float(2)},
{"width", fl_value_new_float(3)},
{"height", fl_value_new_float(4)},
});
g_autoptr(GBytes) set_marked_text_rect = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setMarkedTextRect", rect, nullptr);
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
EXPECT_CALL(context, gtk_im_context_set_cursor_location(
::testing::Eq<GtkIMContext*>(context),
::testing::Pointee(::testing::AllOf(
::testing::Field(&GdkRectangle::x, 27),
::testing::Field(&GdkRectangle::y, 32),
::testing::Field(&GdkRectangle::width, 0),
::testing::Field(&GdkRectangle::height, 0)))));
messenger.ReceiveMessage("flutter/textinput", set_marked_text_rect);
}
TEST(FlTextInputPluginTest, TextInputTypeNone) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
g_autoptr(FlValue) args = build_input_config({
.client_id = 1,
.input_type = "TextInputType.none",
});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
@ -80,5 +753,212 @@ TEST(FlTextInputPluginTest, SetClient) {
SuccessResponse(null), ::testing::A<GError**>()))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", message);
messenger.ReceiveMessage("flutter/textinput", set_client);
EXPECT_CALL(context,
gtk_im_context_focus_in(::testing::Eq<GtkIMContext*>(context)))
.Times(0);
EXPECT_CALL(context,
gtk_im_context_focus_out(::testing::Eq<GtkIMContext*>(context)));
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
g_autoptr(GBytes) show = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.show", nullptr, nullptr);
messenger.ReceiveMessage("flutter/textinput", show);
}
TEST(FlTextInputPluginTest, TextEditingDelta) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
// set config
g_autoptr(FlValue) args = build_input_config({
.client_id = 1,
.enable_delta_model = true,
});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::A<FlBinaryMessengerResponseHandle*>(),
SuccessResponse(null), ::testing::A<GError**>()))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_client);
// set editing state
g_autoptr(FlValue) state = build_editing_state({
.text = "Flutter",
.selection_base = 7,
.selection_extent = 7,
});
g_autoptr(GBytes) set_state = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setEditingState", state, nullptr);
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_state);
// update editing state with deltas
g_autoptr(FlValue) deltas = build_list({
fl_value_new_int(1), // client_id
build_map({{
"deltas",
build_list({
build_editing_delta({
.old_text = "Flutter",
.delta_text = "Flutter",
.delta_start = 7,
.delta_end = 7,
.selection_base = 0,
.selection_extent = 0,
}),
}),
}}),
});
EXPECT_CALL(messenger,
fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingStateWithDeltas",
FlValueEq(deltas)),
::testing::_, ::testing::_, ::testing::_));
send_key_event(plugin, GDK_KEY_Home);
}
TEST(FlTextInputPluginTest, ComposingDelta) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
::testing::NiceMock<flutter::testing::MockIMContext> context;
g_autoptr(FlTextInputPlugin) plugin =
fl_text_input_plugin_new(messenger, context);
EXPECT_NE(plugin, nullptr);
// set config
g_autoptr(FlValue) args = build_input_config({
.client_id = 1,
.enable_delta_model = true,
});
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) set_client = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "TextInput.setClient", args, nullptr);
g_autoptr(FlValue) null = fl_value_new_null();
EXPECT_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::A<FlBinaryMessengerResponseHandle*>(),
SuccessResponse(null), ::testing::A<GError**>()))
.WillOnce(::testing::Return(true));
messenger.ReceiveMessage("flutter/textinput", set_client);
// update
EXPECT_CALL(context,
gtk_im_context_get_preedit_string(
::testing::Eq<GtkIMContext*>(context),
::testing::A<gchar**>(), ::testing::_, ::testing::A<gint*>()))
.WillOnce(
::testing::DoAll(::testing::SetArgPointee<1>(g_strdup("Flutter ")),
::testing::SetArgPointee<3>(8)));
g_autoptr(FlValue) update = build_list({
fl_value_new_int(1), // client_id
build_map({{
"deltas",
build_list({
build_editing_delta({
.old_text = "",
.delta_text = "Flutter ",
.delta_start = 0,
.delta_end = 8,
.selection_base = 8,
.selection_extent = 8,
.composing_base = 0,
.composing_extent = 8,
}),
}),
}}),
});
EXPECT_CALL(messenger,
fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingStateWithDeltas",
FlValueEq(update)),
::testing::_, ::testing::_, ::testing::_));
g_signal_emit_by_name(context, "preedit-changed", nullptr);
// commit
g_autoptr(FlValue) commit = build_list({
fl_value_new_int(1), // client_id
build_map({{
"deltas",
build_list({
build_editing_delta({
.old_text = "Flutter ",
.delta_text = "engine",
.delta_start = 8,
.delta_end = 8,
.selection_base = 14,
.selection_extent = 14,
.composing_base = 0,
.composing_extent = 8,
}),
}),
}}),
});
EXPECT_CALL(messenger,
fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingStateWithDeltas",
FlValueEq(commit)),
::testing::_, ::testing::_, ::testing::_));
g_signal_emit_by_name(context, "commit", "engine", nullptr);
// end
g_autoptr(FlValue) end = build_list({
fl_value_new_int(1), // client_id
build_map({{
"deltas",
build_list({
build_editing_delta({
.delta_text = "Flutter engine",
.selection_base = 14,
.selection_extent = 14,
}),
}),
}}),
});
EXPECT_CALL(messenger,
fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/textinput"),
MethodCall("TextInputClient.updateEditingStateWithDeltas",
FlValueEq(end)),
::testing::_, ::testing::_, ::testing::_));
g_signal_emit_by_name(context, "preedit-end", nullptr);
}

View File

@ -92,20 +92,16 @@ G_DEFINE_TYPE_WITH_CODE(
G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(),
fl_view_scrolling_delegate_iface_init))
static gboolean text_input_im_filter_by_gtk(GtkIMContext* im_context,
gpointer gdk_event) {
GdkEventKey* event = reinterpret_cast<GdkEventKey*>(gdk_event);
GdkEventType type = event->type;
g_return_val_if_fail(type == GDK_KEY_PRESS || type == GDK_KEY_RELEASE, false);
return gtk_im_context_filter_keypress(im_context, event);
}
// Initialize keyboard manager.
static void init_keyboard(FlView* self) {
FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine);
self->text_input_plugin = fl_text_input_plugin_new(
messenger, gtk_widget_get_window(GTK_WIDGET(self)),
text_input_im_filter_by_gtk);
GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(self));
g_return_if_fail(GDK_IS_WINDOW(window));
g_autoptr(GtkIMContext) im_context = gtk_im_multicontext_new();
gtk_im_context_set_client_window(im_context, window);
self->text_input_plugin = fl_text_input_plugin_new(messenger, im_context);
self->keyboard_manager =
fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(self));
}
@ -242,6 +238,7 @@ static void on_pre_engine_restart_cb(FlEngine* engine, gpointer user_data) {
FlView* self = FL_VIEW(user_data);
g_clear_object(&self->keyboard_manager);
g_clear_object(&self->text_input_plugin);
g_clear_object(&self->scrolling_manager);
init_keyboard(self);
init_scrolling(self);
@ -488,7 +485,6 @@ static void fl_view_constructed(GObject* object) {
// Create system channel handlers.
FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine);
self->accessibility_plugin = fl_accessibility_plugin_new(self);
init_keyboard(self);
init_scrolling(self);
self->mouse_cursor_plugin = fl_mouse_cursor_plugin_new(messenger, self);
self->platform_plugin = fl_platform_plugin_new(messenger);
@ -622,6 +618,8 @@ static void fl_view_realize(GtkWidget* widget) {
gtk_widget_set_window(widget, window);
gtk_widget_register_window(widget, window);
init_keyboard(self);
if (!fl_renderer_start(self->renderer, self, &error)) {
g_warning("Failed to start Flutter renderer: %s", error->message);
return;

View File

@ -0,0 +1,121 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/testing/mock_im_context.h"
using namespace flutter::testing;
G_DECLARE_FINAL_TYPE(FlMockIMContext,
fl_mock_im_context,
FL,
MOCK_IM_CONTEXT,
GtkIMContext)
struct _FlMockIMContext {
GtkIMContext parent_instance;
MockIMContext* mock;
};
G_DEFINE_TYPE(FlMockIMContext, fl_mock_im_context, GTK_TYPE_IM_CONTEXT)
static void fl_mock_im_context_set_client_window(GtkIMContext* context,
GdkWindow* window) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_set_client_window(context, window);
}
static void fl_mock_im_context_get_preedit_string(GtkIMContext* context,
gchar** str,
PangoAttrList** attrs,
gint* cursor_pos) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_get_preedit_string(context, str, attrs,
cursor_pos);
}
static gboolean fl_mock_im_context_filter_keypress(GtkIMContext* context,
GdkEventKey* event) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
return self->mock->gtk_im_context_filter_keypress(context, event);
}
static void fl_mock_im_context_focus_in(GtkIMContext* context) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_focus_in(context);
}
static void fl_mock_im_context_focus_out(GtkIMContext* context) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_focus_out(context);
}
static void fl_mock_im_context_reset(GtkIMContext* context) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_reset(context);
}
static void fl_mock_im_context_set_cursor_location(GtkIMContext* context,
GdkRectangle* area) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_set_cursor_location(context, area);
}
static void fl_mock_im_context_set_use_preedit(GtkIMContext* context,
gboolean use_preedit) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_set_use_preedit(context, use_preedit);
}
static void fl_mock_im_context_set_surrounding(GtkIMContext* context,
const gchar* text,
gint len,
gint cursor_index) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
self->mock->gtk_im_context_set_surrounding(context, text, len, cursor_index);
}
static gboolean fl_mock_im_context_get_surrounding(GtkIMContext* context,
gchar** text,
gint* cursor_index) {
FlMockIMContext* self = FL_MOCK_IM_CONTEXT(context);
return self->mock->gtk_im_context_get_surrounding(context, text,
cursor_index);
}
static void fl_mock_im_context_class_init(FlMockIMContextClass* klass) {
GtkIMContextClass* im_context_class = GTK_IM_CONTEXT_CLASS(klass);
im_context_class->set_client_window = fl_mock_im_context_set_client_window;
im_context_class->get_preedit_string = fl_mock_im_context_get_preedit_string;
im_context_class->filter_keypress = fl_mock_im_context_filter_keypress;
im_context_class->focus_in = fl_mock_im_context_focus_in;
im_context_class->focus_out = fl_mock_im_context_focus_out;
im_context_class->reset = fl_mock_im_context_reset;
im_context_class->set_cursor_location =
fl_mock_im_context_set_cursor_location;
im_context_class->set_use_preedit = fl_mock_im_context_set_use_preedit;
im_context_class->set_surrounding = fl_mock_im_context_set_surrounding;
im_context_class->get_surrounding = fl_mock_im_context_get_surrounding;
}
static void fl_mock_im_context_init(FlMockIMContext* self) {}
static GtkIMContext* fl_mock_im_context_new(MockIMContext* mock) {
FlMockIMContext* self =
FL_MOCK_IM_CONTEXT(g_object_new(fl_mock_im_context_get_type(), nullptr));
self->mock = mock;
return GTK_IM_CONTEXT(self);
}
MockIMContext::~MockIMContext() {
if (FL_IS_MOCK_IM_CONTEXT(instance_)) {
g_clear_object(&instance_);
}
}
MockIMContext::operator GtkIMContext*() {
if (instance_ == nullptr) {
instance_ = fl_mock_im_context_new(this);
}
return instance_;
}

View File

@ -0,0 +1,54 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_IM_CONTEXT_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_IM_CONTEXT_H_
#include <gtk/gtk.h>
#include "gmock/gmock.h"
namespace flutter {
namespace testing {
class MockIMContext {
public:
~MockIMContext();
operator GtkIMContext*();
MOCK_METHOD2(gtk_im_context_set_client_window,
void(GtkIMContext* context, GdkWindow* window));
MOCK_METHOD4(gtk_im_context_get_preedit_string,
void(GtkIMContext* context,
gchar** str,
PangoAttrList** attrs,
gint* cursor_pos));
MOCK_METHOD2(gtk_im_context_filter_keypress,
gboolean(GtkIMContext* context, GdkEventKey* event));
MOCK_METHOD1(gtk_im_context_focus_in, gboolean(GtkIMContext* context));
MOCK_METHOD1(gtk_im_context_focus_out, void(GtkIMContext* context));
MOCK_METHOD1(gtk_im_context_reset, void(GtkIMContext* context));
MOCK_METHOD2(gtk_im_context_set_cursor_location,
void(GtkIMContext* context, GdkRectangle* area));
MOCK_METHOD2(gtk_im_context_set_use_preedit,
void(GtkIMContext* context, gboolean use_preedit));
MOCK_METHOD4(gtk_im_context_set_surrounding,
void(GtkIMContext* context,
const gchar* text,
gint len,
gint cursor_index));
MOCK_METHOD3(gtk_im_context_get_surrounding,
gboolean(GtkIMContext* context,
gchar** text,
gint* cursor_index));
private:
GtkIMContext* instance_ = nullptr;
};
} // namespace testing
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_IM_CONTEXT_H_