blob: e5fe1d7963bb67b2c56ffb423385809823ed0d0d [file] [log] [blame]
// 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 <gtk/gtk.h>
#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
#include "flutter/shell/platform/linux/fl_method_codec_private.h"
#include "flutter/shell/platform/linux/fl_platform_plugin.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h"
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h"
#include "flutter/testing/testing.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
MATCHER_P(SuccessResponse, result, "") {
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(FlMethodResponse) response =
fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr);
if (fl_value_equal(fl_method_response_get_result(response, nullptr),
result)) {
return true;
}
*result_listener << ::testing::PrintToString(response);
return false;
}
class MethodCallMatcher {
public:
using is_gtest_matcher = void;
explicit MethodCallMatcher(::testing::Matcher<std::string> name,
::testing::Matcher<FlValue*> args)
: name_(std::move(name)), args_(std::move(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_;
};
static ::testing::Matcher<GBytes*> MethodCall(
const std::string& name,
::testing::Matcher<FlValue*> args) {
return MethodCallMatcher(::testing::StrEq(name), std::move(args));
}
MATCHER_P(FlValueEq, value, "equal to " + ::testing::PrintToString(value)) {
return fl_value_equal(arg, value);
}
G_DECLARE_FINAL_TYPE(FlTestApplication,
fl_test_application,
FL,
TEST_APPLICATION,
GtkApplication)
struct _FlTestApplication {
GtkApplication parent_instance;
gboolean* dispose_called;
};
G_DEFINE_TYPE(FlTestApplication,
fl_test_application,
gtk_application_get_type())
static void fl_test_application_startup(GApplication* application) {
G_APPLICATION_CLASS(fl_test_application_parent_class)->startup(application);
// Add a window to this application, which will hold a reference to the
// application and stop it disposing. See
// https://gitlab.gnome.org/GNOME/gtk/-/issues/6190
gtk_application_window_new(GTK_APPLICATION(application));
}
static void fl_test_application_activate(GApplication* application) {
G_APPLICATION_CLASS(fl_test_application_parent_class)->activate(application);
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
g_autoptr(FlPlatformPlugin) plugin = fl_platform_plugin_new(messenger);
EXPECT_NE(plugin, nullptr);
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(FlValue) exit_result = fl_value_new_map();
fl_value_set_string_take(exit_result, "response",
fl_value_new_string("exit"));
EXPECT_CALL(messenger,
fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger), ::testing::_,
SuccessResponse(exit_result), ::testing::_))
.WillOnce(::testing::Return(true));
// Request app exit.
g_autoptr(FlValue) args = fl_value_new_map();
fl_value_set_string_take(args, "type", fl_value_new_string("required"));
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr);
messenger.ReceiveMessage("flutter/platform", message);
}
static void fl_test_application_dispose(GObject* object) {
FlTestApplication* self = FL_TEST_APPLICATION(object);
*self->dispose_called = true;
G_OBJECT_CLASS(fl_test_application_parent_class)->dispose(object);
}
static void fl_test_application_class_init(FlTestApplicationClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_test_application_dispose;
G_APPLICATION_CLASS(klass)->startup = fl_test_application_startup;
G_APPLICATION_CLASS(klass)->activate = fl_test_application_activate;
}
static void fl_test_application_init(FlTestApplication* self) {}
FlTestApplication* fl_test_application_new(gboolean* dispose_called) {
FlTestApplication* self = FL_TEST_APPLICATION(
g_object_new(fl_test_application_get_type(), nullptr));
// Don't try and register on D-Bus.
g_application_set_application_id(G_APPLICATION(self), "dev.flutter.GtkTest");
g_application_set_flags(G_APPLICATION(self), G_APPLICATION_NON_UNIQUE);
// Added to stop compiler complaining about an unused function.
FL_IS_TEST_APPLICATION(self);
self->dispose_called = dispose_called;
return self;
}
TEST(FlPlatformPluginTest, PlaySound) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
g_autoptr(FlPlatformPlugin) plugin = fl_platform_plugin_new(messenger);
EXPECT_NE(plugin, nullptr);
g_autoptr(FlValue) args = fl_value_new_string("SystemSoundType.alert");
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "SystemSound.play", 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/platform", message);
}
TEST(FlPlatformPluginTest, ExitApplication) {
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
g_autoptr(FlPlatformPlugin) plugin = fl_platform_plugin_new(messenger);
EXPECT_NE(plugin, nullptr);
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
g_autoptr(FlValue) null = fl_value_new_null();
ON_CALL(messenger, fl_binary_messenger_send_response(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::_, SuccessResponse(null), ::testing::_))
.WillByDefault(testing::Return(TRUE));
// Indicate that the binding is initialized.
g_autoptr(GError) error = nullptr;
g_autoptr(GBytes) init_message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "System.initializationComplete", nullptr, &error);
messenger.ReceiveMessage("flutter/platform", init_message);
g_autoptr(FlValue) request_args = fl_value_new_map();
fl_value_set_string_take(request_args, "type",
fl_value_new_string("cancelable"));
EXPECT_CALL(messenger,
fl_binary_messenger_send_on_channel(
::testing::Eq<FlBinaryMessenger*>(messenger),
::testing::StrEq("flutter/platform"),
MethodCall("System.requestAppExit", FlValueEq(request_args)),
::testing::_, ::testing::_, ::testing::_));
g_autoptr(FlValue) args = fl_value_new_map();
fl_value_set_string_take(args, "type", fl_value_new_string("cancelable"));
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr);
messenger.ReceiveMessage("flutter/platform", message);
}
TEST(FlPlatformPluginTest, ExitApplicationDispose) {
gtk_init(0, nullptr);
gboolean dispose_called = false;
FlTestApplication* application = fl_test_application_new(&dispose_called);
// Run the application, it will quit after startup.
g_application_run(G_APPLICATION(application), 0, nullptr);
EXPECT_FALSE(dispose_called);
g_object_unref(application);
EXPECT_TRUE(dispose_called);
}