=== modified file 'Makefile.am'
--- Makefile.am	2013-08-20 10:38:10 +0000
+++ Makefile.am	2015-03-10 21:21:53 +0000
@@ -12,10 +12,10 @@
 
 if BUILD_TESTS
 SUBDIRS += \
-	test
+	tests
 
 # build src first
-test: src
+tests: src libmessaging-menu
 
 endif
 

=== modified file 'configure.ac'
--- configure.ac	2014-09-17 20:13:13 +0000
+++ configure.ac	2015-03-10 21:21:53 +0000
@@ -48,6 +48,8 @@
 
 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/categories/Makefile
 data/upstart/Makefile
 po/Makefile.in
-test/Makefile
+tests/Makefile
 libmessaging-menu/Makefile
 libmessaging-menu/messaging-menu.pc
 doc/Makefile

=== modified file 'debian/control'
--- debian/control	2014-10-08 14:45:43 +0000
+++ debian/control	2015-03-10 21:21:53 +0000
@@ -11,6 +11,7 @@
                gtk-doc-tools,
                intltool,
                libaccountsservice-dev,
+               libdbustest1-dev,
                libgirepository1.0-dev (>= 0.9.12),
                libgtest-dev,
                python3-dbusmock,

=== modified file 'po/POTFILES.in'
--- po/POTFILES.in	2013-10-01 10:29:49 +0000
+++ po/POTFILES.in	2015-03-10 21:21:53 +0000
@@ -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

=== renamed directory 'test' => 'tests'
=== removed directory 'tests'
=== modified file 'tests/Makefile.am'
--- test/Makefile.am	2013-08-28 10:23:31 +0000
+++ tests/Makefile.am	2015-03-10 21:21:53 +0000
@@ -1,5 +1,6 @@
 
-check_LIBRARIES = libgtest.a
+CLEANFILES=
+check_LTLIBRARIES = libgtest.la
 check_PROGRAMS = test-gactionmuxer
 
 TESTS = $(check_PROGRAMS)
@@ -7,15 +8,22 @@
 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_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 @@
 	$(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 = \

=== added file 'tests/accounts-service-mock.h'
--- tests/accounts-service-mock.h	1970-01-01 00:00:00 +0000
+++ tests/accounts-service-mock.h	2015-03-10 21:21:53 +0000
@@ -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;
+		}
+};

=== modified file 'tests/applications/test.desktop'
--- test/applications/test.desktop	2013-02-27 00:34:14 +0000
+++ tests/applications/test.desktop	2015-03-10 21:21:53 +0000
@@ -1,2 +1,5 @@
 [Desktop Entry]
 Type=Application
+Name=Test
+Exec=test
+Icon=test-app

=== added file 'tests/applications/test2.desktop'
--- tests/applications/test2.desktop	1970-01-01 00:00:00 +0000
+++ tests/applications/test2.desktop	2015-03-10 21:21:53 +0000
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Type=Application
+Name=Test
+Exec=test
+Icon=test-app

=== added file 'tests/indicator-fixture.h'
--- tests/indicator-fixture.h	1970-01-01 00:00:00 +0000
+++ tests/indicator-fixture.h	2015-03-10 21:21:53 +0000
@@ -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)

=== added file 'tests/indicator-test.cpp'
--- tests/indicator-test.cpp	1970-01-01 00:00:00 +0000
+++ tests/indicator-test.cpp	2015-03-10 21:21:53 +0000
@@ -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);
+}

=== renamed file 'tests/manual' => 'tests/manual'
