diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | test/applications/test.desktop | 2 | ||||
-rw-r--r-- | tests/Makefile.am (renamed from test/Makefile.am) | 58 | ||||
-rw-r--r-- | tests/accounts-service-mock.h | 134 | ||||
-rw-r--r-- | tests/applications/test.desktop | 5 | ||||
-rw-r--r-- | tests/applications/test2.desktop | 5 | ||||
-rw-r--r-- | tests/indicator-fixture.h | 688 | ||||
-rw-r--r-- | tests/indicator-messages-service-activate.build.sh (renamed from test/indicator-messages-service-activate.build.sh) | 0 | ||||
-rw-r--r-- | tests/indicator-messages-service-activate.c (renamed from test/indicator-messages-service-activate.c) | 0 | ||||
-rw-r--r-- | tests/indicator-test.cpp | 209 | ||||
-rwxr-xr-x | tests/test-client.py (renamed from test/test-client.py) | 0 | ||||
-rw-r--r-- | tests/test-gactionmuxer.cpp (renamed from test/test-gactionmuxer.cpp) | 0 |
15 files changed, 1099 insertions, 12 deletions
diff --git a/Makefile.am b/Makefile.am index f8141a8..a48804e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,10 +12,10 @@ endif if BUILD_TESTS SUBDIRS += \ - test + tests # build src first -test: src +tests: src libmessaging-menu endif diff --git a/configure.ac b/configure.ac index 8550030..b9c8800 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,8 @@ PKG_CHECK_MODULES(APPLET, gio-unix-2.0 >= $GIO_UNIX_REQUIRED_VERSION PKG_CHECK_MODULES(GIO, gio-unix-2.0 >= $GIO_UNIX_REQUIRED_VERSION) +PKG_CHECK_MODULES(DBUSTEST, dbustest-1) + AC_SUBST(APPLET_CFLAGS) AC_SUBST(APPLET_LIBS) @@ -172,7 +174,7 @@ data/icons/scalable/status/Makefile data/icons/scalable/categories/Makefile data/upstart/Makefile po/Makefile.in -test/Makefile +tests/Makefile libmessaging-menu/Makefile libmessaging-menu/messaging-menu.pc doc/Makefile diff --git a/debian/control b/debian/control index 5cdba7d..eccb874 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Build-Depends: debhelper (>= 9), gtk-doc-tools, intltool, libaccountsservice-dev, + libdbustest1-dev, libgirepository1.0-dev (>= 0.9.12), libgtest-dev, python3-dbusmock, diff --git a/po/POTFILES.in b/po/POTFILES.in index 6cb028f..4cb6565 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,5 +1,4 @@ [encoding: UTF-8] -test/indicator-messages-service-activate.c src/im-phone-menu.c src/messages-service.c src/im-desktop-menu.c diff --git a/test/applications/test.desktop b/test/applications/test.desktop deleted file mode 100644 index c2332b9..0000000 --- a/test/applications/test.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Type=Application diff --git a/test/Makefile.am b/tests/Makefile.am index b679615..bd559fd 100644 --- a/test/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,6 @@ -check_LIBRARIES = libgtest.a +CLEANFILES= +check_LTLIBRARIES = libgtest.la check_PROGRAMS = test-gactionmuxer TESTS = $(check_PROGRAMS) @@ -7,15 +8,22 @@ TESTS = $(check_PROGRAMS) AM_CPPFLAGS = $(GTEST_CPPFLAGS) \ -I${top_srcdir}/src -nodist_libgtest_a_SOURCES = \ +###################################### +# Google Test +###################################### + +nodist_libgtest_la_SOURCES = \ $(GTEST_SOURCE)/src/gtest-all.cc \ $(GTEST_SOURCE)/src/gtest_main.cc -libgtest_a_CPPFLAGS = \ +libgtest_la_CPPFLAGS = \ $(GTEST_CPPFLAGS) -w \ $(AM_CPPFLAGS) -libgtest_a_CXXFLAGS = \ +libgtest_la_CXXFLAGS = \ $(AM_CXXFLAGS) +###################################### +# GActionMixer +###################################### test_gactionmuxer_SOURCES = \ test-gactionmuxer.cpp @@ -27,8 +35,46 @@ test_gactionmuxer_CPPFLAGS = \ test_gactionmuxer_LDADD = \ $(APPLET_LIBS) \ libindicator-messages-service.la \ - libgtest.a + 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 @@ -41,7 +87,7 @@ 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/gactionmuxer.h \ $(top_srcdir)/src/dbus-data.h libindicator_messages_service_ladir = \ 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> ¶meter) { + 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/test/indicator-messages-service-activate.build.sh b/tests/indicator-messages-service-activate.build.sh index 87a0316..87a0316 100644 --- a/test/indicator-messages-service-activate.build.sh +++ b/tests/indicator-messages-service-activate.build.sh diff --git a/test/indicator-messages-service-activate.c b/tests/indicator-messages-service-activate.c index f5a26b0..f5a26b0 100644 --- a/test/indicator-messages-service-activate.c +++ b/tests/indicator-messages-service-activate.c 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/test/test-client.py b/tests/test-client.py index 0dbf868..0dbf868 100755 --- a/test/test-client.py +++ b/tests/test-client.py diff --git a/test/test-gactionmuxer.cpp b/tests/test-gactionmuxer.cpp index 5c98c90..5c98c90 100644 --- a/test/test-gactionmuxer.cpp +++ b/tests/test-gactionmuxer.cpp |