aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile.am123
-rw-r--r--tests/accounts-service-mock.h134
-rw-r--r--tests/applications/test.desktop5
-rw-r--r--tests/applications/test2.desktop5
-rw-r--r--tests/indicator-fixture.h688
-rw-r--r--tests/indicator-messages-service-activate.build.sh3
-rw-r--r--tests/indicator-messages-service-activate.c55
-rw-r--r--tests/indicator-test.cpp209
-rw-r--r--tests/manual47
-rwxr-xr-xtests/test-client.py80
-rw-r--r--tests/test-gactionmuxer.cpp407
11 files changed, 1756 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..bd559fd
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,123 @@
+
+CLEANFILES=
+check_LTLIBRARIES = libgtest.la
+check_PROGRAMS = test-gactionmuxer
+
+TESTS = $(check_PROGRAMS)
+
+AM_CPPFLAGS = $(GTEST_CPPFLAGS) \
+ -I${top_srcdir}/src
+
+######################################
+# Google Test
+######################################
+
+nodist_libgtest_la_SOURCES = \
+ $(GTEST_SOURCE)/src/gtest-all.cc \
+ $(GTEST_SOURCE)/src/gtest_main.cc
+libgtest_la_CPPFLAGS = \
+ $(GTEST_CPPFLAGS) -w \
+ $(AM_CPPFLAGS)
+libgtest_la_CXXFLAGS = \
+ $(AM_CXXFLAGS)
+
+######################################
+# GActionMixer
+######################################
+
+test_gactionmuxer_SOURCES = \
+ test-gactionmuxer.cpp
+
+test_gactionmuxer_CPPFLAGS = \
+ $(APPLET_CFLAGS) \
+ $(AM_CPPFLAGS)
+
+test_gactionmuxer_LDADD = \
+ $(APPLET_LIBS) \
+ libindicator-messages-service.la \
+ libgtest.la
+
+######################################
+# Indicator Test
+######################################
+
+SCHEMA_COMPILED_DIR="$(abs_builddir)/gsettings-schemas-compiled/"
+
+indicator_test_SOURCES = \
+ indicator-test.cpp
+
+indicator_test_CPPFLAGS = \
+ -DINDICATOR_MESSAGES_SERVICE_BINARY="\"$(abs_top_builddir)/src/indicator-messages-service\"" \
+ -DSCHEMA_DIR="\"$(SCHEMA_COMPILED_DIR)\"" \
+ -DXDG_DATA_DIRS="\"$(abs_srcdir)/\"" \
+ -I$(top_srcdir)/libmessaging-menu \
+ -std=c++11 \
+ $(APPLET_CFLAGS) \
+ $(DBUSTEST_CFLAGS) \
+ $(AM_CPPFLAGS)
+
+indicator_test_LDADD = \
+ $(APPLET_LIBS) \
+ $(DBUSTEST_LIBS) \
+ $(top_builddir)/libmessaging-menu/libmessaging-menu.la \
+ libgtest.la \
+ -lc -lpthread
+
+indicator-test.cpp: schemas-compiled.stamp
+
+schemas-compiled.stamp: $(top_srcdir)/data/*gschema.xml
+ @rm -rf $(SCHEMA_COMPILED_DIR)
+ @mkdir -p $(SCHEMA_COMPILED_DIR)
+ @cp -f $(top_srcdir)/data/*gschema.xml $(SCHEMA_COMPILED_DIR)
+ @`pkg-config gio-2.0 --variable glib_compile_schemas` $(SCHEMA_COMPILED_DIR)
+ @touch schemas-compiled.stamp
+
+CLEANFILES += schemas-compiled.stamp
+TESTS += indicator-test
+check_PROGRAMS += indicator-test
+
+######################################
+# Lib containing code under test
+######################################
+
+noinst_LTLIBRARIES = \
+ libindicator-messages-service.la
+
+libindicator_messages_service_la_SOURCES = \
+ $(top_builddir)/common/indicator-messages-service.c \
+ $(top_builddir)/common/indicator-messages-service.h \
+ $(top_srcdir)/src/gactionmuxer.c \
+ $(top_srcdir)/src/gactionmuxer.h \
+ $(top_srcdir)/src/dbus-data.h
+
+libindicator_messages_service_ladir = \
+ $(includedir)/libindicator-messages-service/
+
+libindicator_messages_service_la_CFLAGS = \
+ $(APPLET_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ -I$(top_builddir)/src \
+ -I$(top_builddir)/common \
+ -Wall \
+ -Wl,-Bsymbolic-functions \
+ -Wl,-z,defs \
+ -Wl,--as-needed \
+ -Werror -Wno-error=deprecated-declarations \
+ -DG_LOG_DOMAIN=\"Indicator-Messages\"
+
+libindicator_messages_service_la_LIBADD = \
+ $(APPLET_LIBS)
+
+libindicator_messages_service_la_LDFLAGS = \
+ $(COVERAGE_LDFLAGS)
+
+######################################
+# Test client with dbusmock
+######################################
+
+TESTS_ENVIRONMENT = \
+ export LD_LIBRARY_PATH=$(top_builddir)/libmessaging-menu/.libs; \
+ export GI_TYPELIB_PATH=$(top_builddir)/libmessaging-menu; \
+ export XDG_DATA_DIRS=$(abs_srcdir);
+
+TESTS += test-client.py
diff --git a/tests/accounts-service-mock.h b/tests/accounts-service-mock.h
new file mode 100644
index 0000000..301fcd4
--- /dev/null
+++ b/tests/accounts-service-mock.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright © 2014 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+#include <memory>
+#include <libdbustest/dbus-test.h>
+
+class AccountsServiceMock
+{
+ DbusTestDbusMock * mock = nullptr;
+ DbusTestDbusMockObject * soundobj = nullptr;
+ DbusTestDbusMockObject * userobj = nullptr;
+ DbusTestDbusMockObject * syssoundobj = nullptr;
+ DbusTestDbusMockObject * privacyobj = nullptr;
+
+ public:
+ AccountsServiceMock () {
+ mock = dbus_test_dbus_mock_new("org.freedesktop.Accounts");
+
+ dbus_test_task_set_bus(DBUS_TEST_TASK(mock), DBUS_TEST_SERVICE_BUS_SYSTEM);
+
+ DbusTestDbusMockObject * baseobj = dbus_test_dbus_mock_get_object(mock, "/org/freedesktop/Accounts", "org.freedesktop.Accounts", NULL);
+
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "CacheUser", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH,
+ "ret = dbus.ObjectPath('/user')\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "FindUserById", G_VARIANT_TYPE_INT64, G_VARIANT_TYPE_OBJECT_PATH,
+ "ret = dbus.ObjectPath('/user')\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "FindUserByName", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH,
+ "ret = dbus.ObjectPath('/user')\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "ListCachedUsers", NULL, G_VARIANT_TYPE_OBJECT_PATH_ARRAY,
+ "ret = [ dbus.ObjectPath('/user') ]\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "UncacheUser", G_VARIANT_TYPE_STRING, NULL,
+ "", NULL);
+
+ userobj = dbus_test_dbus_mock_get_object(mock, "/user", "org.freedesktop.Accounts.User", NULL);
+ dbus_test_dbus_mock_object_add_property(mock, userobj,
+ "UserName", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(g_get_user_name()), NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "SetXHasMessages", G_VARIANT_TYPE_BOOLEAN, nullptr,
+ "", NULL);
+
+ soundobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.canonical.indicator.sound.AccountsService", NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Timestamp", G_VARIANT_TYPE_UINT64,
+ g_variant_new_uint64(0), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "PlayerName", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "PlayerIcon", G_VARIANT_TYPE_VARIANT,
+ g_variant_new_variant(g_variant_new_string("")), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Running", G_VARIANT_TYPE_BOOLEAN,
+ g_variant_new_boolean(FALSE), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "State", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Title", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Artist", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Album", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "ArtUrl", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+
+ syssoundobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.ubuntu.touch.AccountsService.Sound", NULL);
+ dbus_test_dbus_mock_object_add_property(mock, syssoundobj,
+ "SilentMode", G_VARIANT_TYPE_BOOLEAN,
+ g_variant_new_boolean(FALSE), NULL);
+
+ privacyobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.ubuntu.touch.AccountsService.SecurityPrivacy", NULL);
+ dbus_test_dbus_mock_object_add_property(mock, privacyobj,
+ "MessagesWelcomeScreen", G_VARIANT_TYPE_BOOLEAN,
+ g_variant_new_boolean(true), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, privacyobj,
+ "StatsWelcomeScreen", G_VARIANT_TYPE_BOOLEAN,
+ g_variant_new_boolean(true), NULL);
+ }
+
+ ~AccountsServiceMock () {
+ g_debug("Destroying the Accounts Service Mock");
+ g_clear_object(&mock);
+ }
+
+ void setSilentMode (bool modeValue) {
+ dbus_test_dbus_mock_object_update_property(mock, syssoundobj,
+ "SilentMode", g_variant_new_boolean(modeValue ? TRUE : FALSE),
+ NULL);
+ }
+
+ operator std::shared_ptr<DbusTestTask> () {
+ return std::shared_ptr<DbusTestTask>(
+ DBUS_TEST_TASK(g_object_ref(mock)),
+ [](DbusTestTask * task) { g_clear_object(&task); });
+ }
+
+ operator DbusTestTask* () {
+ return DBUS_TEST_TASK(mock);
+ }
+
+ operator DbusTestDbusMock* () {
+ return mock;
+ }
+
+ DbusTestDbusMockObject * get_sound () {
+ return soundobj;
+ }
+};
diff --git a/tests/applications/test.desktop b/tests/applications/test.desktop
new file mode 100644
index 0000000..63ccb6b
--- /dev/null
+++ b/tests/applications/test.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Type=Application
+Name=Test
+Exec=test
+Icon=test-app
diff --git a/tests/applications/test2.desktop b/tests/applications/test2.desktop
new file mode 100644
index 0000000..63ccb6b
--- /dev/null
+++ b/tests/applications/test2.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Type=Application
+Name=Test
+Exec=test
+Icon=test-app
diff --git a/tests/indicator-fixture.h b/tests/indicator-fixture.h
new file mode 100644
index 0000000..e2c4316
--- /dev/null
+++ b/tests/indicator-fixture.h
@@ -0,0 +1,688 @@
+/*
+ * Copyright © 2014 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+#include <memory>
+#include <algorithm>
+#include <string>
+#include <functional>
+#include <future>
+
+#include <gtest/gtest.h>
+#include <gio/gio.h>
+#include <libdbustest/dbus-test.h>
+
+class IndicatorFixture : public ::testing::Test
+{
+ private:
+ std::string _indicatorPath;
+ std::string _indicatorAddress;
+ std::vector<std::shared_ptr<DbusTestTask>> _mocks;
+ protected:
+ std::chrono::milliseconds _eventuallyTime;
+
+ private:
+ class PerRunData {
+ public:
+ /* We're private in the fixture but other than that we don't care,
+ we don't leak out. This object's purpose isn't to hide data it is
+ to make the lifecycle of the items more clear. */
+ std::shared_ptr<GMenuModel> _menu;
+ std::shared_ptr<GActionGroup> _actions;
+ DbusTestService * _session_service;
+ DbusTestService * _system_service;
+ DbusTestTask * _test_indicator;
+ DbusTestTask * _test_dummy;
+ GDBusConnection * _session;
+ GDBusConnection * _system;
+
+ PerRunData (const std::string& indicatorPath, const std::string& indicatorAddress, std::vector<std::shared_ptr<DbusTestTask>>& mocks)
+ : _menu(nullptr)
+ , _session(nullptr)
+ {
+ _session_service = dbus_test_service_new(nullptr);
+ dbus_test_service_set_bus(_session_service, DBUS_TEST_SERVICE_BUS_SESSION);
+
+ _system_service = dbus_test_service_new(nullptr);
+ dbus_test_service_set_bus(_system_service, DBUS_TEST_SERVICE_BUS_SYSTEM);
+
+ _test_indicator = DBUS_TEST_TASK(dbus_test_process_new(indicatorPath.c_str()));
+ dbus_test_task_set_name(_test_indicator, "Indicator");
+ dbus_test_service_add_task(_session_service, _test_indicator);
+
+ _test_dummy = dbus_test_task_new();
+ dbus_test_task_set_wait_for(_test_dummy, indicatorAddress.c_str());
+ dbus_test_task_set_name(_test_dummy, "Dummy");
+ dbus_test_service_add_task(_session_service, _test_dummy);
+
+ for(auto task : mocks) {
+ if (dbus_test_task_get_bus(task.get()) == DBUS_TEST_SERVICE_BUS_SYSTEM) {
+ dbus_test_service_add_task(_system_service, task.get());
+ } else {
+ dbus_test_service_add_task(_session_service, task.get());
+ }
+ }
+
+ g_debug("Starting System Bus");
+ dbus_test_service_start_tasks(_system_service);
+ _system = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr);
+ g_dbus_connection_set_exit_on_close(_system, FALSE);
+
+ g_debug("Starting Session Bus");
+ dbus_test_service_start_tasks(_session_service);
+ _session = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
+ g_dbus_connection_set_exit_on_close(_session, FALSE);
+ }
+
+ virtual ~PerRunData (void) {
+ _menu.reset();
+ _actions.reset();
+
+ /* D-Bus Test Stuff */
+ g_clear_object(&_test_dummy);
+ g_clear_object(&_test_indicator);
+ g_clear_object(&_session_service);
+ g_clear_object(&_system_service);
+
+ /* Wait for D-Bus session bus to go */
+ if (!g_dbus_connection_is_closed(_session)) {
+ g_dbus_connection_close_sync(_session, nullptr, nullptr);
+ }
+ g_clear_object(&_session);
+
+ if (!g_dbus_connection_is_closed(_system)) {
+ g_dbus_connection_close_sync(_system, nullptr, nullptr);
+ }
+ g_clear_object(&_system);
+ }
+ };
+
+ std::shared_ptr<PerRunData> run;
+
+ public:
+ virtual ~IndicatorFixture() = default;
+
+ IndicatorFixture (const std::string& path,
+ const std::string& addr)
+ : _indicatorPath(path)
+ , _indicatorAddress(addr)
+ , _eventuallyTime(std::chrono::seconds(5))
+ {
+ };
+
+
+ protected:
+ virtual void SetUp() override
+ {
+ run = std::make_shared<PerRunData>(_indicatorPath, _indicatorAddress, _mocks);
+
+ _mocks.clear();
+ }
+
+ virtual void TearDown() override
+ {
+ run.reset();
+ }
+
+ void addMock (std::shared_ptr<DbusTestTask> mock)
+ {
+ _mocks.push_back(mock);
+ }
+
+ std::shared_ptr<DbusTestTask> buildBustleMock (const std::string& filename, DbusTestServiceBus bus = DBUS_TEST_SERVICE_BUS_BOTH)
+ {
+ return std::shared_ptr<DbusTestTask>([filename, bus]() {
+ DbusTestTask * bustle = DBUS_TEST_TASK(dbus_test_bustle_new(filename.c_str()));
+ dbus_test_task_set_name(bustle, "Bustle");
+ dbus_test_task_set_bus(bustle, bus);
+ return bustle;
+ }(), [](DbusTestTask * bustle) {
+ g_clear_object(&bustle);
+ });
+ }
+
+ private:
+ void waitForCore (GObject * obj, const gchar * signalname) {
+ auto loop = g_main_loop_new(nullptr, FALSE);
+
+ /* Our two exit criteria */
+ gulong signal = g_signal_connect_swapped(obj, signalname, G_CALLBACK(g_main_loop_quit), loop);
+ guint timer = g_timeout_add_seconds(5, [](gpointer user_data) -> gboolean {
+ g_warning("Menu Timeout");
+ g_main_loop_quit((GMainLoop *)user_data);
+ return G_SOURCE_CONTINUE;
+ }, loop);
+
+ /* Wait for sync */
+ g_main_loop_run(loop);
+
+ /* Clean up */
+ g_source_remove(timer);
+ g_signal_handler_disconnect(obj, signal);
+
+ g_main_loop_unref(loop);
+ }
+
+ void menuWaitForItems (const std::shared_ptr<GMenuModel>& menu) {
+ auto count = g_menu_model_get_n_items(menu.get());
+
+ if (count != 0)
+ return;
+
+ waitForCore(G_OBJECT(menu.get()), "items-changed");
+ }
+
+ void agWaitForActions (const std::shared_ptr<GActionGroup>& group) {
+ auto list = std::shared_ptr<gchar *>(
+ g_action_group_list_actions(group.get()),
+ [](gchar ** list) {
+ g_strfreev(list);
+ });
+
+ if (g_strv_length(list.get()) != 0) {
+ return;
+ }
+
+ waitForCore(G_OBJECT(group.get()), "action-added");
+ }
+
+ testing::AssertionResult expectEventually (std::function<testing::AssertionResult(void)> &testfunc) {
+ auto loop = std::shared_ptr<GMainLoop>(g_main_loop_new(nullptr, FALSE), [](GMainLoop * loop) { if (loop != nullptr) g_main_loop_unref(loop); });
+
+ std::promise<testing::AssertionResult> retpromise;
+ auto retfuture = retpromise.get_future();
+ auto start = std::chrono::steady_clock::now();
+
+ /* The core of the idle function as an object so we can use the C++-isms
+ of attaching the variables and make this code reasonably readable */
+ std::function<void(void)> idlefunc = [&loop, &retpromise, &testfunc, &start, this]() -> void {
+ auto result = testfunc();
+
+ if (result == false && _eventuallyTime > (std::chrono::steady_clock::now() - start)) {
+ return;
+ }
+
+ retpromise.set_value(result);
+ g_main_loop_quit(loop.get());
+ };
+
+ auto idlesrc = g_idle_add([](gpointer data) -> gboolean {
+ auto func = reinterpret_cast<std::function<void(void)> *>(data);
+ (*func)();
+ return G_SOURCE_CONTINUE;
+ }, &idlefunc);
+
+ g_main_loop_run(loop.get());
+ g_source_remove(idlesrc);
+
+ return retfuture.get();
+ }
+
+ protected:
+ void setMenu (const std::string& path) {
+ run->_menu.reset();
+
+ g_debug("Getting Menu: %s:%s", _indicatorAddress.c_str(), path.c_str());
+ run->_menu = std::shared_ptr<GMenuModel>(G_MENU_MODEL(g_dbus_menu_model_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GMenuModel * modelptr) {
+ g_clear_object(&modelptr);
+ });
+
+ menuWaitForItems(run->_menu);
+ }
+
+ void setActions (const std::string& path) {
+ run->_actions.reset();
+
+ run->_actions = std::shared_ptr<GActionGroup>(G_ACTION_GROUP(g_dbus_action_group_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GActionGroup * groupptr) {
+ g_clear_object(&groupptr);
+ });
+
+ agWaitForActions(run->_actions);
+ }
+
+ void activateAction (const std::string &name, std::shared_ptr<GVariant> &parameter) {
+ g_action_group_activate_action(run->_actions.get(), name.c_str(), parameter.get());
+ }
+
+ void activateAction (const std::string &name, GVariant * parameter = nullptr) {
+ std::shared_ptr<GVariant> param;
+
+ if (parameter != nullptr)
+ param = std::shared_ptr<GVariant>(g_variant_ref_sink(parameter), [](GVariant * var) {
+ g_variant_unref(var);
+ });
+
+ return activateAction(name, param);
+ }
+
+ testing::AssertionResult expectActionExists (const gchar * nameStr, const std::string& name) {
+ bool hasit = g_action_group_has_action(run->_actions.get(), name.c_str());
+
+ if (!hasit) {
+ auto result = testing::AssertionFailure();
+ result <<
+ " Action: " << nameStr << std::endl <<
+ " Expected: " << "Exists" << std::endl <<
+ " Actual: " << "No action found" << std::endl;
+
+ return result;
+ }
+
+ auto result = testing::AssertionSuccess();
+ return result;
+ }
+
+ template <typename... Args> testing::AssertionResult expectEventuallyActionExists (Args&& ... args) {
+ std::function<testing::AssertionResult(void)> func = [&]() {
+ return expectActionExists(std::forward<Args>(args)...);
+ };
+ return expectEventually(func);
+ }
+
+ testing::AssertionResult expectActionDoesNotExist (const gchar * nameStr, const std::string& name) {
+ bool hasit = g_action_group_has_action(run->_actions.get(), name.c_str());
+
+ if (hasit) {
+ auto result = testing::AssertionFailure();
+ result <<
+ " Action: " << nameStr << std::endl <<
+ " Expected: " << "No action found" << std::endl <<
+ " Actual: " << "Exists" << std::endl;
+
+ return result;
+ }
+
+ auto result = testing::AssertionSuccess();
+ return result;
+ }
+
+ template <typename... Args> testing::AssertionResult expectEventuallyActionDoesNotExist (Args&& ... args) {
+ std::function<testing::AssertionResult(void)> func = [&]() {
+ return expectActionDoesNotExist(std::forward<Args>(args)...);
+ };
+ return expectEventually(func);
+ }
+
+ testing::AssertionResult expectActionEnabled (const char * nameStr, const char * typeStr, const std::string& name, bool enabled) {
+ auto aenabled = g_action_group_get_action_enabled(run->_actions.get(), name.c_str());
+
+ if (enabled != aenabled) {
+ auto result = testing::AssertionFailure();
+ result <<
+ " Action: " << nameStr << std::endl <<
+ " Expected: " << enabled << std::endl <<
+ " Actual: " << aenabled << std::endl;
+
+ return result;
+ }
+
+ auto result = testing::AssertionSuccess();
+ return result;
+ }
+
+ template <typename... Args> testing::AssertionResult expectEventuallyActionEnabled (Args&& ... args) {
+ std::function<testing::AssertionResult(void)> func = [&]() {
+ return expectActionEnabled(std::forward<Args>(args)...);
+ };
+ return expectEventually(func);
+ }
+
+ testing::AssertionResult expectActionStateType (const char * nameStr, const char * typeStr, const std::string& name, const GVariantType * type) {
+ auto atype = g_action_group_get_action_state_type(run->_actions.get(), name.c_str());
+ bool same = false;
+
+ if (atype != nullptr) {
+ same = g_variant_type_equal(atype, type);
+ }
+
+ if (!same) {
+ auto result = testing::AssertionFailure();
+ result <<
+ " Action: " << nameStr << std::endl <<
+ " Expected: " << typeStr << std::endl <<
+ " Actual: " << (atype == nullptr ? "(null)" : g_variant_type_peek_string(atype)) << std::endl;
+
+ return result;
+ }
+
+ auto result = testing::AssertionSuccess();
+ return result;
+ }
+
+ template <typename... Args> testing::AssertionResult expectEventuallyActionStateType (Args&& ... args) {
+ std::function<testing::AssertionResult(void)> func = [&]() {
+ return expectActionStateType(std::forward<Args>(args)...);
+ };
+ return expectEventually(func);
+ }
+
+ testing::AssertionResult expectActionActivationType (const char * nameStr, const char * typeStr, const std::string& name, const GVariantType * type) {
+ auto atype = g_action_group_get_action_parameter_type(run->_actions.get(), name.c_str());
+ bool same = false;
+
+ if (atype != nullptr) {
+ same = g_variant_type_equal(atype, type);
+ }
+
+ if (!same) {
+ auto result = testing::AssertionFailure();
+ result <<
+ " Action: " << nameStr << std::endl <<
+ " Expected: " << typeStr << std::endl <<
+ " Actual: " << (atype == nullptr ? "(null)" : g_variant_type_peek_string(atype)) << std::endl;
+
+ return result;
+ }
+
+ auto result = testing::AssertionSuccess();
+ return result;
+ }
+
+ template <typename... Args> testing::AssertionResult expectEventuallyActionActivationType (Args&& ... args) {
+ std::function<testing::AssertionResult(void)> func = [&]() {
+ return expectActionActivationType(std::forward<Args>(args)...);
+ };
+ return expectEventually(func);
+ }
+
+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, std::shared_ptr<GVariant> varref) {
+ auto aval = std::shared_ptr<GVariant>(g_action_group_get_action_state(run->_actions.get(), name.c_str()), [] (GVariant * varptr) {
+ if (varptr != nullptr)
+ g_variant_unref(varptr);
+ });
+ bool match = false;
+
+ if (aval != nullptr) {
+ match = g_variant_equal(aval.get(), varref.get());
+ }
+
+ if (!match) {
+ gchar * attstr = nullptr;
+
+ if (aval != nullptr) {
+ attstr = g_variant_print(aval.get(), TRUE);
+ } else {
+ attstr = g_strdup("nullptr");
+ }
+
+ auto result = testing::AssertionFailure();
+ result <<
+ " Action: " << nameStr << std::endl <<
+ " Expected: " << valueStr << std::endl <<
+ " Actual: " << attstr << std::endl;
+
+ g_free(attstr);
+
+ return result;
+ } else {
+ auto result = testing::AssertionSuccess();
+ return result;
+ }
+ }
+
+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, GVariant * value) {
+ auto varref = std::shared_ptr<GVariant>(g_variant_ref_sink(value), [](GVariant * varptr) {
+ if (varptr != nullptr)
+ g_variant_unref(varptr);
+ });
+ return expectActionStateIs(nameStr, valueStr, name, varref);
+ }
+
+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, bool value) {
+ GVariant * var = g_variant_new_boolean(value);
+ return expectActionStateIs(nameStr, valueStr, name, var);
+ }
+
+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, std::string value) {
+ GVariant * var = g_variant_new_string(value.c_str());
+ return expectActionStateIs(nameStr, valueStr, name, var);
+ }
+
+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, const char * value) {
+ GVariant * var = g_variant_new_string(value);
+ return expectActionStateIs(nameStr, valueStr, name, var);
+ }
+
+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, double value) {
+ GVariant * var = g_variant_new_double(value);
+ return expectActionStateIs(nameStr, valueStr, name, var);
+ }
+
+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, float value) {
+ GVariant * var = g_variant_new_double(value);
+ return expectActionStateIs(nameStr, valueStr, name, var);
+ }
+
+ template <typename... Args> testing::AssertionResult expectEventuallyActionStateIs (Args&& ... args) {
+ std::function<testing::AssertionResult(void)> func = [&]() {
+ return expectActionStateIs(std::forward<Args>(args)...);
+ };
+ return expectEventually(func);
+ }
+
+
+ private:
+ std::shared_ptr<GVariant> getMenuAttributeVal (int location, std::shared_ptr<GMenuModel>& menu, const std::string& attribute, std::shared_ptr<GVariant>& value) {
+ if (!(location < g_menu_model_get_n_items(menu.get()))) {
+ return nullptr;
+ }
+
+ if (location >= g_menu_model_get_n_items(menu.get()))
+ return nullptr;
+
+ auto menuval = std::shared_ptr<GVariant>(g_menu_model_get_item_attribute_value(menu.get(), location, attribute.c_str(), g_variant_get_type(value.get())), [](GVariant * varptr) {
+ if (varptr != nullptr)
+ g_variant_unref(varptr);
+ });
+
+ return menuval;
+ }
+
+ std::shared_ptr<GVariant> getMenuAttributeRecurse (std::vector<int>::const_iterator menuLocation, std::vector<int>::const_iterator menuEnd, const std::string& attribute, std::shared_ptr<GVariant>& value, std::shared_ptr<GMenuModel>& menu) {
+ if (menuLocation == menuEnd)
+ return nullptr;
+
+ if (menuLocation + 1 == menuEnd)
+ return getMenuAttributeVal(*menuLocation, menu, attribute, value);
+
+ auto clearfunc = [](GMenuModel * modelptr) {
+ g_clear_object(&modelptr);
+ };
+
+ auto submenu = std::shared_ptr<GMenuModel>(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SUBMENU), clearfunc);
+
+ if (submenu == nullptr)
+ submenu = std::shared_ptr<GMenuModel>(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SECTION), clearfunc);
+
+ if (submenu == nullptr)
+ return nullptr;
+
+ menuWaitForItems(submenu);
+
+ return getMenuAttributeRecurse(menuLocation + 1, menuEnd, attribute, value, submenu);
+ }
+
+ protected:
+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, GVariant * value) {
+ auto varref = std::shared_ptr<GVariant>(g_variant_ref_sink(value), [](GVariant * varptr) {
+ if (varptr != nullptr)
+ g_variant_unref(varptr);
+ });
+
+ auto attrib = getMenuAttributeRecurse(menuLocation.cbegin(), menuLocation.cend(), attribute, varref, run->_menu);
+ bool same = false;
+
+ if (attrib != nullptr && varref != nullptr) {
+ same = g_variant_equal(attrib.get(), varref.get());
+ }
+
+ if (!same) {
+ gchar * attstr = nullptr;
+
+ if (attrib != nullptr) {
+ attstr = g_variant_print(attrib.get(), TRUE);
+ } else {
+ attstr = g_strdup("nullptr");
+ }
+
+ auto result = testing::AssertionFailure();
+ result <<
+ " Menu: " << menuLocationStr << std::endl <<
+ " Attribute: " << attributeStr << std::endl <<
+ " Expected: " << valueStr << std::endl <<
+ " Actual: " << attstr << std::endl;
+
+ g_free(attstr);
+
+ return result;
+ } else {
+ auto result = testing::AssertionSuccess();
+ return result;
+ }
+ }
+
+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, bool value) {
+ GVariant * var = g_variant_new_boolean(value);
+ return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var);
+ }
+
+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, std::string value) {
+ GVariant * var = g_variant_new_string(value.c_str());
+ return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var);
+ }
+
+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, const char * value) {
+ GVariant * var = g_variant_new_string(value);
+ return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var);
+ }
+
+ template <typename... Args> testing::AssertionResult expectEventuallyMenuAttribute (Args&& ... args) {
+ std::function<testing::AssertionResult(void)> func = [&]() {
+ return expectMenuAttribute(std::forward<Args>(args)...);
+ };
+ return expectEventually(func);
+ }
+
+ /* Eventually Helpers */
+ #define _EVENTUALLY_HELPER(oper) \
+ template <typename... Args> testing::AssertionResult expectEventually##oper (Args&& ... args) { \
+ std::function<testing::AssertionResult(void)> func = [&]() { \
+ return testing::internal::CmpHelper##oper(std::forward<Args>(args)...); \
+ }; \
+ return expectEventually(func); \
+ }
+
+ _EVENTUALLY_HELPER(EQ);
+ _EVENTUALLY_HELPER(NE);
+ _EVENTUALLY_HELPER(LT);
+ _EVENTUALLY_HELPER(GT);
+ _EVENTUALLY_HELPER(STREQ);
+ _EVENTUALLY_HELPER(STRNE);
+
+ #undef _EVENTUALLY_HELPER
+};
+
+/* Menu Attrib */
+#define ASSERT_MENU_ATTRIB(menu, attrib, value) \
+ ASSERT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value)
+
+#define EXPECT_MENU_ATTRIB(menu, attrib, value) \
+ EXPECT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value)
+
+#define EXPECT_EVENTUALLY_MENU_ATTRIB(menu, attrib, value) \
+ EXPECT_PRED_FORMAT3(IndicatorFixture::expectEventuallyMenuAttribute, menu, attrib, value)
+
+/* Action Exists */
+#define ASSERT_ACTION_EXISTS(action) \
+ ASSERT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action)
+
+#define EXPECT_ACTION_EXISTS(action) \
+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action)
+
+#define EXPECT_EVENTUALLY_ACTION_EXISTS(action) \
+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectEventuallyActionExists, action)
+
+/* Action Does Not Exist */
+#define ASSERT_ACTION_DOES_NOT_EXIST(action) \
+ ASSERT_PRED_FORMAT1(IndicatorFixture::expectActionDoesNotExist, action)
+
+#define EXPECT_ACTION_DOES_NOT_EXIST(action) \
+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectActionDoesNotExist, action)
+
+#define EXPECT_EVENTUALLY_ACTION_DOES_NOT_EXIST(action) \
+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectEventuallyActionDoesNotExist, action)
+
+/* Action Enabled */
+#define ASSERT_ACTION_ENABLED(action, state) \
+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionEnabled, action, state)
+
+#define EXPECT_ACTION_ENABLED(action, state) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionEnabled, action, state)
+
+#define EXPECT_EVENTUALLY_ACTION_ENABLED(action, state) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionEnabled, action, state)
+
+/* Action State */
+#define ASSERT_ACTION_STATE(action, value) \
+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value)
+
+#define EXPECT_ACTION_STATE(action, value) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value)
+
+#define EXPECT_EVENTUALLY_ACTION_STATE(action, value) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionStateIs, action, value)
+
+/* Action State Type */
+#define ASSERT_ACTION_STATE_TYPE(action, type) \
+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type)
+
+#define EXPECT_ACTION_STATE_TYPE(action, type) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type)
+
+#define EXPECT_EVENTUALLY_ACTION_STATE_TYPE(action, type) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionStateType, action, type)
+
+/* Action Activation Type */
+#define ASSERT_ACTION_ACTIVATION_TYPE(action, type) \
+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionActivationType, action, type)
+
+#define EXPECT_ACTION_ACTIVATION_TYPE(action, type) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionActivationType, action, type)
+
+#define EXPECT_EVENTUALLY_ACTION_ACTIVATION_TYPE(action, type) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionActivationType, action, type)
+
+/* Helpers */
+
+#define EXPECT_EVENTUALLY_EQ(expected, actual) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyEQ, expected, actual)
+
+#define EXPECT_EVENTUALLY_NE(expected, actual) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyNE, expected, actual)
+
+#define EXPECT_EVENTUALLY_LT(expected, actual) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyLT, expected, actual)
+
+#define EXPECT_EVENTUALLY_GT(expected, actual) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyGT, expected, actual)
+
+#define EXPECT_EVENTUALLY_STREQ(expected, actual) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallySTREQ, expected, actual)
+
+#define EXPECT_EVENTUALLY_STRNE(expected, actual) \
+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallySTRNE, expected, actual)
diff --git a/tests/indicator-messages-service-activate.build.sh b/tests/indicator-messages-service-activate.build.sh
new file mode 100644
index 0000000..87a0316
--- /dev/null
+++ b/tests/indicator-messages-service-activate.build.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+gcc -o indicator-messages-service-activate indicator-messages-service-activate.c `pkg-config --cflags --libs dbus-1 dbus-glib-1`
diff --git a/tests/indicator-messages-service-activate.c b/tests/indicator-messages-service-activate.c
new file mode 100644
index 0000000..f5a26b0
--- /dev/null
+++ b/tests/indicator-messages-service-activate.c
@@ -0,0 +1,55 @@
+/*
+An indicator to show information that is in messaging applications
+that the user is using.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include "../src/dbus-data.h"
+
+int
+main (int argc, char ** argv)
+{
+#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34
+ g_type_init();
+#endif
+
+ guint returnval = 0;
+ GError * error = NULL;
+
+ DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
+ DBusGProxy * proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+
+ g_debug("Activating service: %s", INDICATOR_MESSAGES_DBUS_NAME);
+ if (!org_freedesktop_DBus_start_service_by_name (proxy, INDICATOR_MESSAGES_DBUS_NAME, 0, &returnval, &error)) {
+ g_error("Unable to send message to DBus to start service: %s", error != NULL ? error->message : "(NULL error)" );
+ g_error_free(error);
+ return 1;
+ }
+
+ if (returnval != DBUS_START_REPLY_SUCCESS && returnval != DBUS_START_REPLY_ALREADY_RUNNING) {
+ g_error("Return value isn't indicative of success: %d", returnval);
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/tests/indicator-test.cpp b/tests/indicator-test.cpp
new file mode 100644
index 0000000..0991db5
--- /dev/null
+++ b/tests/indicator-test.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright © 2015 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+#include <gtest/gtest.h>
+#include <gio/gio.h>
+
+#include "indicator-fixture.h"
+#include "accounts-service-mock.h"
+
+#include "messaging-menu-app.h"
+#include "messaging-menu-message.h"
+
+class IndicatorTest : public IndicatorFixture
+{
+protected:
+ IndicatorTest (void) :
+ IndicatorFixture(INDICATOR_MESSAGES_SERVICE_BINARY, "com.canonical.indicator.messages")
+ {
+ }
+
+ std::shared_ptr<AccountsServiceMock> as;
+
+ virtual void SetUp() override
+ {
+ g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE);
+ g_setenv("GSETTINGS_BACKEND", "memory", TRUE);
+
+ g_setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, TRUE);
+
+ as = std::make_shared<AccountsServiceMock>();
+ addMock(*as);
+
+ IndicatorFixture::SetUp();
+ }
+
+ virtual void TearDown() override
+ {
+ as.reset();
+
+ IndicatorFixture::TearDown();
+ }
+
+};
+
+
+TEST_F(IndicatorTest, RootAction) {
+ setActions("/com/canonical/indicator/messages");
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("messages");
+ EXPECT_ACTION_STATE_TYPE("messages", G_VARIANT_TYPE("a{sv}"));
+ EXPECT_ACTION_STATE("messages", g_variant_new_parsed("{'icon': <('themed', <['indicator-messages-offline', 'indicator-messages', 'indicator']>)>, 'title': <'Notifications'>, 'accessible-desc': <'Messages'>, 'visible': <false>}"));
+}
+
+TEST_F(IndicatorTest, SingleMessage) {
+ setActions("/com/canonical/indicator/messages");
+
+ auto app = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
+ ASSERT_NE(nullptr, app);
+ messaging_menu_app_register(app.get());
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.launch");
+
+ auto msg = std::shared_ptr<MessagingMenuMessage>(messaging_menu_message_new(
+ "testid",
+ nullptr, /* no icon */
+ "Test Title",
+ "A subtitle too",
+ "You only like me for my body",
+ 0), [](MessagingMenuMessage * msg) { g_clear_object(&msg); });
+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg.testid");
+
+ setMenu("/com/canonical/indicator/messages/phone");
+
+ EXPECT_EVENTUALLY_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-type", "com.canonical.indicator.messages.messageitem");
+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "label", "Test Title");
+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-message-id", "testid");
+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-subtitle", "A subtitle too");
+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-text", "You only like me for my body");
+}
+
+static void
+messageReplyActivate (GObject * obj, gchar * name, GVariant * value, gpointer user_data) {
+ auto res = reinterpret_cast<std::string *>(user_data);
+ *res = g_variant_get_string(value, nullptr);
+}
+
+TEST_F(IndicatorTest, MessageReply) {
+ setActions("/com/canonical/indicator/messages");
+
+ auto app = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
+ ASSERT_NE(nullptr, app);
+ messaging_menu_app_register(app.get());
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.launch");
+
+ auto msg = std::shared_ptr<MessagingMenuMessage>(messaging_menu_message_new(
+ "messageid",
+ nullptr, /* no icon */
+ "Reply Message",
+ "A message to reply to",
+ "In-app replies are for wimps, reply here to save yourself time and be cool.",
+ 0), [](MessagingMenuMessage * msg) { g_clear_object(&msg); });
+ messaging_menu_message_add_action(msg.get(),
+ "replyid",
+ "Reply",
+ G_VARIANT_TYPE_STRING,
+ nullptr);
+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg.messageid");
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg-actions.messageid.replyid");
+ EXPECT_ACTION_ACTIVATION_TYPE("test.msg-actions.messageid.replyid", G_VARIANT_TYPE_STRING);
+
+ EXPECT_ACTION_ENABLED("remove-all", true);
+
+ setMenu("/com/canonical/indicator/messages/phone");
+
+ EXPECT_EVENTUALLY_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-type", "com.canonical.indicator.messages.messageitem");
+
+ std::string activateResponse;
+ g_signal_connect(msg.get(), "activate", G_CALLBACK(messageReplyActivate), &activateResponse);
+
+ activateAction("test.msg-actions.messageid.replyid", g_variant_new_string("Reply to me"));
+
+ EXPECT_EVENTUALLY_EQ("Reply to me", activateResponse);
+
+ EXPECT_EVENTUALLY_ACTION_ENABLED("remove-all", false);
+}
+
+TEST_F(IndicatorTest, IconNotification) {
+ auto normalicon = std::shared_ptr<GVariant>(g_variant_ref_sink(g_variant_new_parsed("{'icon': <('themed', <['indicator-messages-offline', 'indicator-messages', 'indicator']>)>, 'title': <'Notifications'>, 'accessible-desc': <'Messages'>, 'visible': <true>}")), [](GVariant *var) {if (var != nullptr) g_variant_unref(var); });
+ auto blueicon = std::shared_ptr<GVariant>(g_variant_ref_sink(g_variant_new_parsed("{'icon': <('themed', <['indicator-messages-new-offline', 'indicator-messages-new', 'indicator-messages', 'indicator']>)>, 'title': <'Notifications'>, 'accessible-desc': <'New Messages'>, 'visible': <true>}")), [](GVariant *var) {if (var != nullptr) g_variant_unref(var); });
+
+ setActions("/com/canonical/indicator/messages");
+
+ auto app = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
+ ASSERT_NE(nullptr, app);
+ messaging_menu_app_register(app.get());
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.launch");
+
+ EXPECT_ACTION_STATE("messages", normalicon);
+
+ auto app2 = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test2.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
+ ASSERT_NE(nullptr, app2);
+ messaging_menu_app_register(app2.get());
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test2.launch");
+
+ messaging_menu_app_append_source_with_count(app2.get(),
+ "countsource",
+ nullptr,
+ "Count Source",
+ 500);
+ messaging_menu_app_draw_attention(app2.get(), "countsource");
+
+ EXPECT_EVENTUALLY_ACTION_STATE("messages", blueicon);
+
+ auto msg = std::shared_ptr<MessagingMenuMessage>(messaging_menu_message_new(
+ "messageid",
+ nullptr, /* no icon */
+ "Message",
+ "A secret message",
+ "asdfa;lkweraoweprijas;dvlknasvdoiewur;aslkd",
+ 0), [](MessagingMenuMessage * msg) { g_clear_object(&msg); });
+ messaging_menu_message_set_draws_attention(msg.get(), true);
+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
+
+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg.messageid");
+ EXPECT_ACTION_STATE("messages", blueicon);
+
+ messaging_menu_app_unregister(app2.get());
+ app2.reset();
+
+ EXPECT_EVENTUALLY_ACTION_DOES_NOT_EXIST("test2.msg.countsource");
+ EXPECT_ACTION_STATE("messages", blueicon);
+
+ messaging_menu_app_remove_message(app.get(), msg.get());
+
+ EXPECT_EVENTUALLY_ACTION_STATE("messages", normalicon);
+ EXPECT_ACTION_ENABLED("remove-all", false);
+
+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
+
+ EXPECT_EVENTUALLY_ACTION_STATE("messages", blueicon);
+ EXPECT_ACTION_ENABLED("remove-all", true);
+
+ activateAction("remove-all");
+
+ EXPECT_EVENTUALLY_ACTION_STATE("messages", normalicon);
+}
diff --git a/tests/manual b/tests/manual
index ed930a9..d638875 100644
--- a/tests/manual
+++ b/tests/manual
@@ -31,3 +31,50 @@ Test-case indicator-messages/unity8-phone-symbolic-icon
<dt>Verify the application icon in the menu item is monochromatic</dt>
<dd>On the right side of the item the application icon should have no color</dd>
</dl>
+
+Test-case indicator-messages/unity8-embedded-greeter
+<dl>
+ <dt>NOTE: Only works with embedded greeter, split greeter will require modifications to this test</dt>
+ <dt>NOTE: Only works on a device that can receive SMS messages</dt>
+ <dt>Ensure System Settings is set to "Show Messages on Greeter"</dt>
+ <dt>Send an SMS to the device</dt>
+ <dd>The notification icon should change color</dd>
+ <dd>There should be an entry in the messaging menu with the SMS message</dd>
+ <dd>The item should include the sender and the start of the message</dd>
+ <dt>Go to the greeter. This can be done by hitting the lock button twice.</dt>
+ <dt>Ensure the messaging menu has the message</dt>
+ <dd>The notification icon should have color</dd>
+ <dd>There should be an entry in the messaging menu with the SMS message</dd>
+ <dd>The item should include the sender and the start of the message</dd>
+ <dt>Clear the message in the greeter</dt>
+ <dd>The message should no longer be in the messaging menu</dd>
+ <dt>Disable System Settings value "Show Messages on Greeter"</dt>
+ <dt>Send an SMS to the device</dt>
+ <dd>The notification icon should change color</dd>
+ <dd>There should be an entry in the messaging menu with the SMS message</dd>
+ <dd>The item should include the sender and the start of the message</dd>
+ <dt>Go to the greeter. This can be done by hitting the lock button twice.</dt>
+ <dt>Ensure the messaging menu has the message, but it does not include the start of the message</dt>
+ <dd>The notification icon should have color</dd>
+ <dd>There should be an entry in the messaging menu with the SMS message</dd>
+ <dd>The item should include the sender but NOT the start of the message</dd>
+ <dt>Clear the message in the greeter</dt>
+ <dd>The message should no longer be in the messaging menu</dd>
+</dl>
+
+Test-case indicator-messages/push-message-twitter
+<dl>
+ <dt>From a shell prompt send a simultated Twitter push notification</dt>
+ <dd>gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/com_2eubuntu_2edeveloper_2ewebapps_2ewebapp_2dtwitter --method com.ubuntu.Postal.Post com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter '"{\"message\": \"foobar\", \"notification\":{\"card\": {\"summary\": \"yes\", \"body\": \"hello\", \"popup\": true, \"persist\": true}}}"'</dd>
+ <dd>The messaging envelope on the panel should change to highlight a message</dd>
+ <dt>Open the messaging menu</dt>
+ <dd>The menu should contain an entry with the Twitter icon for the application</dd>
+ <dd>The title of the message should be 'yes'</dd>
+ <dd>The body of the message should be 'hello'</dd>
+ <dd>At the bottom of them menu there should be a 'Clear All' menu item</dd>
+ <dt>Clear the message using the 'Clear All' command</dt>
+ <dd>The Twitter message should disappear</dd>
+ <dd>The 'Clear All' item should disappear</dd>
+ <dd>The icon in the panel should return to its original state</dd>
+</dl>
+
diff --git a/tests/test-client.py b/tests/test-client.py
new file mode 100755
index 0000000..0dbf868
--- /dev/null
+++ b/tests/test-client.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+
+import unittest
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+import dbusmock
+import subprocess
+from gi.repository import GLib, Gio, MessagingMenu
+
+DBusGMainLoop(set_as_default=True)
+
+class MessagingMenuTest(dbusmock.DBusTestCase):
+ @classmethod
+ def setUpClass(klass):
+ klass.start_session_bus()
+ klass.bus = klass.get_dbus(False)
+
+ def setUp(self):
+ name = 'com.canonical.indicator.messages'
+ obj_path = '/com/canonical/indicator/messages/service'
+ iface = 'com.canonical.indicator.messages.service'
+
+ self.messaging_service = self.spawn_server(name, obj_path, iface, stdout=subprocess.PIPE)
+ self.mock = dbus.Interface(self.bus.get_object(name, obj_path), dbusmock.MOCK_IFACE)
+ self.mock.AddMethod('', 'RegisterApplication', 'so', '', '')
+ self.mock.AddMethod('', 'UnregisterApplication', 's', '', '')
+ self.mock.AddMethod('', 'ApplicationStoppedRunning', 's', '', '')
+ self.mock.AddMethod('', 'SetStatus', 'ss', '', '')
+
+ self.loop = GLib.MainLoop()
+
+ def tearDown(self):
+ self.messaging_service.terminate()
+ self.messaging_service.wait()
+
+ def assertArgumentsEqual(self, args, *expected_args):
+ self.assertEqual(len(args), len(expected_args))
+ for i in range(len(args)):
+ if expected_args[i]:
+ self.assertEqual(args[i], expected_args[i])
+
+ def assertMethodCalled(self, name, *expected_args):
+ # set a flag on timeout, assertions don't get bubbled up through c functions
+ self.timed_out = False
+ def timeout(): self.timed_out = True
+ timeout_id = GLib.timeout_add_seconds(10, timeout)
+ while 1:
+ calls = self.mock.GetMethodCalls(name)
+ if len(calls) > 0:
+ GLib.source_remove(timeout_id)
+ self.assertArgumentsEqual(calls[0][1], *expected_args)
+ break
+ GLib.MainContext.default().iteration(True)
+ if self.timed_out:
+ raise self.failureException('method %s was not called after 10 seconds' % name)
+
+ def test_registration(self):
+ mmapp = MessagingMenu.App.new('test.desktop')
+ mmapp.register()
+ self.assertMethodCalled('RegisterApplication', 'test.desktop', None)
+
+ mmapp.unregister()
+ self.assertMethodCalled('UnregisterApplication', 'test.desktop')
+
+ # ApplicationStoppedRunning is called when the last ref on mmapp is dropped
+ # Since mmapp is the only thing holding on to a GDBusConnection, the
+ # connection might get freed before it sends the StoppedRunning
+ # message. Flush the connection to make sure it is sent.
+ bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
+ bus.flush_sync(None)
+ del mmapp
+ self.assertMethodCalled('ApplicationStoppedRunning', 'test.desktop')
+
+ def test_status(self):
+ mmapp = MessagingMenu.App.new('test.desktop')
+ mmapp.register()
+ mmapp.set_status(MessagingMenu.Status.AWAY)
+ self.assertMethodCalled('SetStatus', 'test.desktop', 'away')
+
+unittest.main(testRunner=unittest.TextTestRunner())
diff --git a/tests/test-gactionmuxer.cpp b/tests/test-gactionmuxer.cpp
new file mode 100644
index 0000000..5c98c90
--- /dev/null
+++ b/tests/test-gactionmuxer.cpp
@@ -0,0 +1,407 @@
+/*
+An indicator to show information that is in messaging applications
+that the user is using.
+
+Copyright 2012 Canonical Ltd.
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "gactionmuxer.h"
+}
+
+static gboolean
+strv_contains (gchar **str_array,
+ const gchar *str)
+{
+ gchar **it;
+
+ for (it = str_array; *it; it++) {
+ if (!g_strcmp0 (*it, str))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+TEST(GActionMuxerTest, Sanity) {
+ GActionMuxer *muxer;
+
+#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34
+ g_type_init ();
+#endif
+
+ g_test_expect_message ("Indicator-Messages", G_LOG_LEVEL_CRITICAL, "*G_IS_ACTION_MUXER*");
+ g_action_muxer_insert (NULL, NULL, NULL);
+ g_test_assert_expected_messages ();
+
+ g_test_expect_message ("Indicator-Messages", G_LOG_LEVEL_CRITICAL, "*G_IS_ACTION_MUXER*");
+ g_action_muxer_remove (NULL, NULL);
+ g_test_assert_expected_messages ();
+
+ muxer = g_action_muxer_new ();
+
+ g_action_muxer_insert (muxer, NULL, NULL);
+ g_action_muxer_remove (muxer, NULL);
+
+ g_test_expect_message ("Indicator-Messages", G_LOG_LEVEL_CRITICAL, "*NULL*");
+ EXPECT_FALSE (g_action_group_has_action (G_ACTION_GROUP (muxer), NULL));
+ g_test_assert_expected_messages ();
+
+ g_test_expect_message ("Indicator-Messages", G_LOG_LEVEL_CRITICAL, "*NULL*");
+ EXPECT_FALSE (g_action_group_get_action_enabled (G_ACTION_GROUP (muxer), NULL));
+ g_test_assert_expected_messages ();
+
+ g_test_expect_message ("Indicator-Messages", G_LOG_LEVEL_CRITICAL, "*NULL*");
+ EXPECT_FALSE (g_action_group_query_action (G_ACTION_GROUP (muxer), NULL, NULL, NULL, NULL, NULL, NULL));
+ g_test_assert_expected_messages ();
+
+ g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*NULL*");
+ g_action_group_activate_action (G_ACTION_GROUP (muxer), NULL, NULL);
+ g_test_assert_expected_messages ();
+
+ g_object_unref (muxer);
+}
+
+TEST(GActionMuxerTest, Empty) {
+ GActionMuxer *muxer;
+ gchar **actions;
+
+#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34
+ g_type_init ();
+#endif
+
+ muxer = g_action_muxer_new ();
+
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer));
+ EXPECT_EQ (0, g_strv_length (actions));
+
+ g_strfreev (actions);
+ g_object_unref (muxer);
+}
+
+TEST(GActionMuxerTest, AddAndRemove) {
+ const GActionEntry entries1[] = { { "one" }, { "two" }, { "three" } };
+ const GActionEntry entries2[] = { { "gb" }, { "es" }, { "fr" } };
+ const GActionEntry entries3[] = { { "foo" }, { "bar" } };
+ GSimpleActionGroup *group1;
+ GSimpleActionGroup *group2;
+ GSimpleActionGroup *group3;
+ GActionMuxer *muxer;
+ gchar **actions;
+
+#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34
+ g_type_init ();
+#endif
+
+ group1 = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group1),
+ entries1,
+ G_N_ELEMENTS (entries1),
+ NULL);
+
+ group2 = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group2),
+ entries2,
+ G_N_ELEMENTS (entries2),
+ NULL);
+
+ group3 = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group3),
+ entries3,
+ G_N_ELEMENTS (entries3),
+ NULL);
+
+ muxer = g_action_muxer_new ();
+ g_action_muxer_insert (muxer, "first", G_ACTION_GROUP (group1));
+ g_action_muxer_insert (muxer, "second", G_ACTION_GROUP (group2));
+ g_action_muxer_insert (muxer, NULL, G_ACTION_GROUP (group3));
+
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer));
+ EXPECT_TRUE (g_action_group_has_action (G_ACTION_GROUP (muxer), "first.one"));
+ EXPECT_FALSE (g_action_group_has_action (G_ACTION_GROUP (muxer), "one"));
+ EXPECT_EQ (8, g_strv_length (actions));
+ EXPECT_TRUE (strv_contains (actions, "first.one"));
+ EXPECT_TRUE (strv_contains (actions, "first.two"));
+ EXPECT_TRUE (strv_contains (actions, "first.three"));
+ EXPECT_TRUE (strv_contains (actions, "second.gb"));
+ EXPECT_TRUE (strv_contains (actions, "second.es"));
+ EXPECT_TRUE (strv_contains (actions, "second.fr"));
+ EXPECT_TRUE (strv_contains (actions, "foo"));
+ EXPECT_TRUE (strv_contains (actions, "bar"));
+ g_strfreev (actions);
+
+ g_action_muxer_remove (muxer, NULL);
+ EXPECT_FALSE (g_action_group_has_action (G_ACTION_GROUP (muxer), "foo"));
+ EXPECT_TRUE (g_action_group_has_action (G_ACTION_GROUP (muxer), "first.one"));
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer));
+ EXPECT_EQ (6, g_strv_length (actions));
+ EXPECT_FALSE (strv_contains (actions, "foo"));
+ EXPECT_TRUE (strv_contains (actions, "first.one"));
+ g_strfreev (actions);
+
+ g_action_muxer_remove (muxer, "first");
+ EXPECT_FALSE (g_action_group_has_action (G_ACTION_GROUP (muxer), "first.two"));
+ EXPECT_TRUE (g_action_group_has_action (G_ACTION_GROUP (muxer), "second.es"));
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer));
+ EXPECT_EQ (3, g_strv_length (actions));
+ EXPECT_FALSE (strv_contains (actions, "first.two"));
+ EXPECT_TRUE (strv_contains (actions, "second.es"));
+ g_strfreev (actions);
+
+ g_action_muxer_insert (muxer, "second", G_ACTION_GROUP (group2));
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer));
+ EXPECT_EQ (3, g_strv_length (actions));
+ g_strfreev (actions);
+
+ g_action_muxer_insert (muxer, NULL, G_ACTION_GROUP (group3));
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer));
+ EXPECT_EQ (5, g_strv_length (actions));
+ g_strfreev (actions);
+
+ g_object_unref (muxer);
+ g_object_unref (group1);
+ g_object_unref (group2);
+ g_object_unref (group3);
+}
+
+static gboolean
+g_variant_equal0 (gconstpointer one,
+ gconstpointer two)
+{
+ if (one == NULL)
+ return two == NULL;
+ else
+ return g_variant_equal (one, two);
+}
+
+TEST(GActionMuxerTest, ActionAttributes) {
+ GSimpleActionGroup *group;
+ GSimpleAction *action;
+ GActionMuxer *muxer;
+ gboolean enabled[2];
+ const GVariantType *param_type[2];
+ const GVariantType *state_type[2];
+ GVariant *state_hint[2];
+ GVariant *state[2];
+
+#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34
+ g_type_init ();
+#endif
+
+ group = g_simple_action_group_new ();
+ action = g_simple_action_new ("one", G_VARIANT_TYPE_STRING);
+ g_action_map_add_action (G_ACTION_MAP(group), G_ACTION (action));
+
+ muxer = g_action_muxer_new ();
+ g_action_muxer_insert (muxer, "first", G_ACTION_GROUP (group));
+
+ /* test two of the convenience functions */
+ EXPECT_TRUE (g_action_group_get_action_enabled (G_ACTION_GROUP (muxer), "first.one"));
+ g_simple_action_set_enabled (action, FALSE);
+ EXPECT_FALSE (g_action_group_get_action_enabled (G_ACTION_GROUP (muxer), "first.one"));
+
+ EXPECT_STREQ ((gchar *) g_action_group_get_action_parameter_type (G_ACTION_GROUP (muxer), "first.one"),
+ (gchar *) G_VARIANT_TYPE_STRING);
+
+ /* query_action */
+ g_action_group_query_action (G_ACTION_GROUP (group), "one",
+ &enabled[0], &param_type[0], &state_type[0], &state_hint[0], &state[0]);
+ g_action_group_query_action (G_ACTION_GROUP (muxer), "first.one",
+ &enabled[1], &param_type[1], &state_type[1], &state_hint[1], &state[1]);
+ EXPECT_EQ (enabled[0], enabled[1]);
+ EXPECT_STREQ ((gchar *) param_type[0], (gchar *) param_type[1]);
+ EXPECT_STREQ ((gchar *) state_type[0], (gchar *) state_type[1]);
+ EXPECT_TRUE (g_variant_equal0 ((gconstpointer) state_hint[0], (gconstpointer) state_hint[1]));
+ EXPECT_TRUE (g_variant_equal0 ((gconstpointer) state[0], (gconstpointer) state[1]));
+
+ g_object_unref (action);
+ g_object_unref (group);
+ g_object_unref (muxer);
+}
+
+typedef struct {
+ gboolean signal_ran;
+ const gchar *name;
+} TestSignalClosure;
+
+static void
+action_added (GActionGroup *group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ TestSignalClosure *c = (TestSignalClosure *)user_data;
+ EXPECT_STREQ (c->name, action_name);
+ c->signal_ran = TRUE;
+}
+
+static void
+action_enabled_changed (GActionGroup *group,
+ gchar *action_name,
+ gboolean enabled,
+ gpointer user_data)
+{
+ TestSignalClosure *c = (TestSignalClosure *)user_data;
+ EXPECT_EQ (enabled, FALSE);
+ c->signal_ran = TRUE;
+}
+
+static void
+action_state_changed (GActionGroup *group,
+ gchar *action_name,
+ GVariant *value,
+ gpointer user_data)
+{
+ TestSignalClosure *c = (TestSignalClosure *)user_data;
+ EXPECT_STREQ (g_variant_get_string (value, NULL), "off");
+ c->signal_ran = TRUE;
+}
+
+static void
+action_removed (GActionGroup *group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ TestSignalClosure *c = (TestSignalClosure *)user_data;
+ EXPECT_STREQ (c->name, action_name);
+ c->signal_ran = TRUE;
+}
+
+TEST(GActionMuxerTest, Signals) {
+ GSimpleActionGroup *group;
+ GSimpleAction *action;
+ GActionMuxer *muxer;
+ TestSignalClosure closure;
+
+ group = g_simple_action_group_new ();
+
+ action = g_simple_action_new ("one", G_VARIANT_TYPE_STRING);
+ g_action_map_add_action (G_ACTION_MAP(group), G_ACTION (action));
+ g_object_unref (action);
+
+ muxer = g_action_muxer_new ();
+
+ g_signal_connect (muxer, "action-added",
+ G_CALLBACK (action_added), (gpointer) &closure);
+ g_signal_connect (muxer, "action-enabled-changed",
+ G_CALLBACK (action_enabled_changed), (gpointer) &closure);
+ g_signal_connect (muxer, "action-state-changed",
+ G_CALLBACK (action_state_changed), (gpointer) &closure);
+ g_signal_connect (muxer, "action-removed",
+ G_CALLBACK (action_removed), (gpointer) &closure);
+
+ /* add the group with "one" action and check whether the signal is emitted */
+ closure.signal_ran = FALSE;
+ closure.name = "first.one";
+ g_action_muxer_insert (muxer, "first", G_ACTION_GROUP (group));
+ EXPECT_TRUE (closure.signal_ran);
+
+ /* add a second action after the group was added to the muxer */
+ closure.signal_ran = FALSE;
+ closure.name = "first.two";
+ action = g_simple_action_new_stateful ("two", G_VARIANT_TYPE_STRING,
+ g_variant_new_string ("on"));
+ g_action_map_add_action (G_ACTION_MAP(group), G_ACTION (action));
+ EXPECT_TRUE (closure.signal_ran);
+
+ /* disable the action */
+ closure.signal_ran = FALSE;
+ g_simple_action_set_enabled (action, FALSE);
+ EXPECT_TRUE (closure.signal_ran);
+
+ /* change its state */
+ closure.signal_ran = FALSE;
+ g_simple_action_set_state (action, g_variant_new_string ("off"));
+ EXPECT_TRUE (closure.signal_ran);
+ g_object_unref (action);
+
+ /* remove the first action */
+ closure.signal_ran = FALSE;
+ closure.name = "first.one";
+ g_action_map_remove_action (G_ACTION_MAP(group), "one");
+ EXPECT_TRUE (closure.signal_ran);
+
+ /* remove the whole group, should be notified about "first.two" */
+ closure.signal_ran = FALSE;
+ closure.name = "first.two";
+ g_action_muxer_remove (muxer, "first");
+ EXPECT_TRUE (closure.signal_ran);
+
+ g_object_unref (group);
+ g_object_unref (muxer);
+}
+
+static void
+action_activated (GSimpleAction *simple,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gboolean *signal_ran = (gboolean *)user_data;
+
+ EXPECT_STREQ (g_variant_get_string (parameter, NULL), "value");
+ *signal_ran = TRUE;
+}
+
+static void
+action_change_state (GSimpleAction *simple,
+ GVariant *value,
+ gpointer user_data)
+{
+ gboolean *signal_ran = (gboolean *)user_data;
+
+ EXPECT_STREQ (g_variant_get_string (value, NULL), "off");
+ *signal_ran = TRUE;
+}
+
+TEST(GActionMuxerTest, ActivateAction) {
+ GSimpleActionGroup *group;
+ GSimpleAction *action;
+ GActionMuxer *muxer;
+ gboolean signal_ran;
+
+ group = g_simple_action_group_new ();
+
+ action = g_simple_action_new ("one", G_VARIANT_TYPE_STRING);
+ g_action_map_add_action (G_ACTION_MAP(group), G_ACTION (action));
+ g_signal_connect (action, "activate",
+ G_CALLBACK (action_activated), (gpointer) &signal_ran);
+ g_object_unref (action);
+
+ action = g_simple_action_new_stateful ("two", NULL,
+ g_variant_new_string ("on"));
+ g_action_map_add_action (G_ACTION_MAP(group), G_ACTION (action));
+ g_signal_connect (action, "change-state",
+ G_CALLBACK (action_change_state), (gpointer) &signal_ran);
+ g_object_unref (action);
+
+ muxer = g_action_muxer_new ();
+ g_action_muxer_insert (muxer, "first", G_ACTION_GROUP (group));
+
+ signal_ran = FALSE;
+ g_action_group_activate_action (G_ACTION_GROUP (muxer), "first.one",
+ g_variant_new_string ("value"));
+ EXPECT_TRUE (signal_ran);
+
+ signal_ran = FALSE;
+ g_action_group_change_action_state (G_ACTION_GROUP (muxer), "first.two",
+ g_variant_new_string ("off"));
+ EXPECT_TRUE (signal_ran);
+
+ g_object_unref (group);
+ g_object_unref (muxer);
+}