diff --git a/src/commoncomponents/ResponsiveImage.qml b/src/commoncomponents/ResponsiveImage.qml
index ee8135965185d53c87eb531bddcb269699252d6f..96528e661b88b2b6b2c279501627823f62dfe2ed 100644
--- a/src/commoncomponents/ResponsiveImage.qml
+++ b/src/commoncomponents/ResponsiveImage.qml
@@ -20,7 +20,8 @@ import QtQuick 2.14
 import QtQuick.Controls 2.14
 import QtGraphicalEffects 1.14
 
-import net.jami.Models 1.0
+import net.jami.Constants 1.0
+import net.jami.Helpers 1.0
 
 Item {
     id: root
diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp
index af7bcb34363d1bc21c67a754babb6b2c7b51d481..e92a981ad27ac109ecca6082e25e9f40e517303b 100644
--- a/src/mainapplication.cpp
+++ b/src/mainapplication.cpp
@@ -25,21 +25,6 @@
 #include "appsettingsmanager.h"
 #include "connectivitymonitor.h"
 #include "systemtray.h"
-#include "namedirectory.h"
-#include "qrimageprovider.h"
-#include "tintedbuttonimageprovider.h"
-#include "avatarimageprovider.h"
-#include "avatarregistry.h"
-
-#include "accountadapter.h"
-#include "avadapter.h"
-#include "calladapter.h"
-#include "contactadapter.h"
-#include "pluginadapter.h"
-#include "messagesadapter.h"
-#include "settingsadapter.h"
-#include "utilsadapter.h"
-#include "conversationsadapter.h"
 
 #include <QAction>
 #include <QCommandLineParser>
@@ -435,55 +420,13 @@ MainApplication::setApplicationFont()
 void
 MainApplication::initQmlLayer()
 {
-    // setup the adapters (their lifetimes are that of MainApplication)
-    auto callAdapter = new CallAdapter(systemTray_.get(), lrcInstance_.data(), this);
-    auto messagesAdapter = new MessagesAdapter(settingsManager_.get(), lrcInstance_.data(), this);
-    auto conversationsAdapter = new ConversationsAdapter(systemTray_.get(),
-                                                         lrcInstance_.data(),
-                                                         this);
-    auto avAdapter = new AvAdapter(lrcInstance_.data(), this);
-    auto contactAdapter = new ContactAdapter(lrcInstance_.data(), this);
-    auto accountAdapter = new AccountAdapter(settingsManager_.get(), lrcInstance_.data(), this);
-    auto utilsAdapter = new UtilsAdapter(systemTray_.get(), lrcInstance_.data(), this);
-    auto settingsAdapter = new SettingsAdapter(settingsManager_.get(), lrcInstance_.data(), this);
-    auto pluginAdapter = new PluginAdapter(lrcInstance_.data(), this);
-
-    // qml adapter registration
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, callAdapter, "CallAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, messagesAdapter, "MessagesAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, conversationsAdapter, "ConversationsAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, avAdapter, "AvAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, contactAdapter, "ContactAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, accountAdapter, "AccountAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, utilsAdapter, "UtilsAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, settingsAdapter, "SettingsAdapter");
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
-
-    auto avatarRegistry = new AvatarRegistry(lrcInstance_.data(), this);
-    QML_REGISTERSINGLETONTYPE_POBJECT(NS_HELPERS, avatarRegistry, "AvatarRegistry");
-
-    // TODO: remove these
-    QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, AVModel, &lrcInstance_->avModel())
-    QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, PluginModel, &lrcInstance_->pluginModel())
-    QML_REGISTERSINGLETONTYPE_CUSTOM(NS_HELPERS, UpdateManager, lrcInstance_->getUpdateManager())
-
-    // register other types that don't require injection(e.g. uncreatables, c++/qml singletons)
-    Utils::registerTypes();
-
-    engine_->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance_.get()));
-    engine_->addImageProvider(QLatin1String("tintedPixmap"),
-                              new TintedButtonImageProvider(lrcInstance_.get()));
-    engine_->addImageProvider(QLatin1String("avatarImage"),
-                              new AvatarImageProvider(lrcInstance_.get()));
-
-    engine_->rootContext()->setContextProperty("ScreenInfo", &screenInfo_);
-    engine_->rootContext()->setContextProperty("LRCInstance", lrcInstance_.get());
-
-    engine_->setObjectOwnership(&lrcInstance_->avModel(), QQmlEngine::CppOwnership);
-    engine_->setObjectOwnership(&lrcInstance_->pluginModel(), QQmlEngine::CppOwnership);
-    engine_->setObjectOwnership(lrcInstance_->getUpdateManager(), QQmlEngine::CppOwnership);
-    engine_->setObjectOwnership(lrcInstance_.get(), QQmlEngine::CppOwnership);
-    engine_->setObjectOwnership(&NameDirectory::instance(), QQmlEngine::CppOwnership);
+    // Expose custom types to the QML engine.
+    Utils::registerTypes(engine_.get(),
+                         systemTray_.get(),
+                         lrcInstance_.get(),
+                         settingsManager_.get(),
+                         &screenInfo_,
+                         this);
 
     engine_->load(QUrl(QStringLiteral("qrc:/src/MainApplicationWindow.qml")));
 }
diff --git a/src/mainview/components/FilesToSendDelegate.qml b/src/mainview/components/FilesToSendDelegate.qml
index 6e667e6ca52c7da56fedc544011c4cd24db4fb28..52e913be3732c45caabff794be501432fe1fdacd 100644
--- a/src/mainview/components/FilesToSendDelegate.qml
+++ b/src/mainview/components/FilesToSendDelegate.qml
@@ -97,7 +97,17 @@ Rectangle {
 
         asynchronous: true
         fillMode: Image.PreserveAspectCrop
-        source: IsImage ? JamiQmlUtils.qmlFilePrefix + FilePath : ""
+        source: {
+            if (!IsImage)
+                return ""
+
+            // :/ -> resource url for test purposes
+            var sourceUrl = FilePath
+            if (!sourceUrl.includes(":/"))
+                return JamiQmlUtils.qmlFilePrefix + sourceUrl
+            else
+                return "qrc" + sourceUrl
+        }
 
         layer.enabled: true
         layer.effect: OpacityMask {
diff --git a/src/mainview/components/MessageBar.qml b/src/mainview/components/MessageBar.qml
index 0648d1c3e4af8312f988a65307d6d17f11bb59ea..8bf09a7e912eb263baa1a401221bf65cf15dbbcd 100644
--- a/src/mainview/components/MessageBar.qml
+++ b/src/mainview/components/MessageBar.qml
@@ -133,6 +133,8 @@ ColumnLayout {
         MessageBarTextArea {
             id: textArea
 
+            objectName: "messageBarTextArea"
+
             Layout.alignment: Qt.AlignVCenter
             Layout.fillWidth: true
             Layout.margins: marginSize / 2
@@ -172,6 +174,8 @@ ColumnLayout {
         PushButton {
             id: sendMessageButton
 
+            objectName: "sendMessageButton"
+
             Layout.alignment: Qt.AlignVCenter
             Layout.rightMargin: visible ? marginSize : 0
             Layout.preferredWidth: scale * JamiTheme.messageWebViewFooterButtonSize
diff --git a/src/mainview/components/MessageWebViewFooter.qml b/src/mainview/components/MessageWebViewFooter.qml
index 14c35e04895d49dbfece32e07025fc58d6129c32..ac60be724a84f4b975c85377364df034ddd42ccb 100644
--- a/src/mainview/components/MessageWebViewFooter.qml
+++ b/src/mainview/components/MessageWebViewFooter.qml
@@ -204,6 +204,8 @@ Rectangle {
         FilesToSendContainer {
             id: dataTransferSendContainer
 
+            objectName: "dataTransferSendContainer"
+
             Layout.alignment: Qt.AlignHCenter
             Layout.preferredWidth: footerColumnLayout.width
             Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
diff --git a/src/mainview/components/WelcomePageQrDialog.qml b/src/mainview/components/WelcomePageQrDialog.qml
index 57a4f3b9c8d5f87ec65ec243bcaaf956cda02099..f0e80738c4b9aba66946281cc31ef353f62e638d 100644
--- a/src/mainview/components/WelcomePageQrDialog.qml
+++ b/src/mainview/components/WelcomePageQrDialog.qml
@@ -22,6 +22,7 @@ import QtQuick.Layouts 1.14
 
 import net.jami.Models 1.0
 import net.jami.Adapters 1.0
+import net.jami.Constants 1.0
 
 import "../../commoncomponents"
 
diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp
index 622cf4872118fc9ce663d5a3035dc54b1d002c3f..304ac3938197bef344bb022004c151a1fa8af3be 100644
--- a/src/messagesadapter.cpp
+++ b/src/messagesadapter.cpp
@@ -356,7 +356,8 @@ MessagesAdapter::pasteKeyDetected()
 void
 MessagesAdapter::userIsComposing(bool isComposing)
 {
-    if (!settingsManager_->getValue(Settings::Key::EnableTypingIndicator).toBool()) {
+    if (!settingsManager_->getValue(Settings::Key::EnableTypingIndicator).toBool()
+        || lrcInstance_->get_selectedConvUid().isEmpty()) {
         return;
     }
     lrcInstance_->getCurrentConversationModel()->setIsComposing(lrcInstance_->get_selectedConvUid(),
diff --git a/src/qmlregister.cpp b/src/qmlregister.cpp
index 96fdca206180d2ee490bb34f2b9f85f0dc2b4d90..b24ca19b3d9a970a6f5dd13dc93246249a5067c6 100644
--- a/src/qmlregister.cpp
+++ b/src/qmlregister.cpp
@@ -18,6 +18,16 @@
 
 #include "qmlregister.h"
 
+#include "accountadapter.h"
+#include "avadapter.h"
+#include "calladapter.h"
+#include "contactadapter.h"
+#include "pluginadapter.h"
+#include "messagesadapter.h"
+#include "settingsadapter.h"
+#include "utilsadapter.h"
+#include "conversationsadapter.h"
+
 #include "accountlistmodel.h"
 #include "accountstomigratelistmodel.h"
 #include "mediacodeclistmodel.h"
@@ -30,7 +40,12 @@
 #include "conversationlistmodelbase.h"
 #include "filestosendlistmodel.h"
 
+#include "qrimageprovider.h"
+#include "tintedbuttonimageprovider.h"
+#include "avatarimageprovider.h"
+#include "avatarregistry.h"
 #include "appsettingsmanager.h"
+#include "mainapplication.h"
 #include "distantrenderer.h"
 #include "namedirectory.h"
 #include "updatemanager.h"
@@ -82,8 +97,43 @@ namespace Utils {
  * This function will expose custom types to the QML engine.
  */
 void
-registerTypes()
+registerTypes(QQmlEngine* engine,
+              SystemTray* systemTray,
+              LRCInstance* lrcInstance,
+              AppSettingsManager* appSettingsManager,
+              ScreenInfo* screenInfo,
+              QObject* parent)
 {
+    // setup the adapters (their lifetimes are that of MainApplication)
+    auto callAdapter = new CallAdapter(systemTray, lrcInstance, parent);
+    auto messagesAdapter = new MessagesAdapter(appSettingsManager, lrcInstance, parent);
+    auto conversationsAdapter = new ConversationsAdapter(systemTray, lrcInstance, parent);
+    auto avAdapter = new AvAdapter(lrcInstance, parent);
+    auto contactAdapter = new ContactAdapter(lrcInstance, parent);
+    auto accountAdapter = new AccountAdapter(appSettingsManager, lrcInstance, parent);
+    auto utilsAdapter = new UtilsAdapter(systemTray, lrcInstance, parent);
+    auto settingsAdapter = new SettingsAdapter(appSettingsManager, lrcInstance, parent);
+    auto pluginAdapter = new PluginAdapter(lrcInstance, parent);
+
+    // qml adapter registration
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, callAdapter, "CallAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, messagesAdapter, "MessagesAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, conversationsAdapter, "ConversationsAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, avAdapter, "AvAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, contactAdapter, "ContactAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, accountAdapter, "AccountAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, utilsAdapter, "UtilsAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, settingsAdapter, "SettingsAdapter");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
+
+    auto avatarRegistry = new AvatarRegistry(lrcInstance, parent);
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_HELPERS, avatarRegistry, "AvatarRegistry");
+
+    // TODO: remove these
+    QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, AVModel, &lrcInstance->avModel())
+    QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, PluginModel, &lrcInstance->pluginModel())
+    QML_REGISTERSINGLETONTYPE_CUSTOM(NS_HELPERS, UpdateManager, lrcInstance->getUpdateManager())
+
     // Hack for QtCreator autocomplete (part 2)
     // https://bugreports.qt.io/browse/QTCREATORBUG-20569
     // Use a dummy object to register the import namespace.
@@ -125,6 +175,8 @@ registerTypes()
     QML_REGISTERSINGLETONTYPE_URL(NS_CONSTANTS, "qrc:/src/constant/JamiTheme.qml", JamiTheme);
     QML_REGISTERSINGLETONTYPE_URL(NS_MODELS, "qrc:/src/constant/JamiQmlUtils.qml", JamiQmlUtils);
     QML_REGISTERSINGLETONTYPE_URL(NS_CONSTANTS, "qrc:/src/constant/JamiStrings.qml", JamiStrings);
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, screenInfo, "ScreenInfo")
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_CONSTANTS, lrcInstance, "LRCInstance")
 
     // C++ singletons
     // TODO: remove this
@@ -154,6 +206,17 @@ registerTypes()
     // Enums
     QML_REGISTERUNCREATABLE(NS_ENUMS, Settings);
     QML_REGISTERUNCREATABLE(NS_ENUMS, NetWorkManager);
+
+    engine->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance));
+    engine->addImageProvider(QLatin1String("tintedPixmap"),
+                              new TintedButtonImageProvider(lrcInstance));
+    engine->addImageProvider(QLatin1String("avatarImage"),
+                              new AvatarImageProvider(lrcInstance));
+
+    engine->setObjectOwnership(&lrcInstance->avModel(), QQmlEngine::CppOwnership);
+    engine->setObjectOwnership(&lrcInstance->pluginModel(), QQmlEngine::CppOwnership);
+    engine->setObjectOwnership(lrcInstance->getUpdateManager(), QQmlEngine::CppOwnership);
+    engine->setObjectOwnership(&NameDirectory::instance(), QQmlEngine::CppOwnership);
 }
 // clang-format on
 } // namespace Utils
diff --git a/src/qmlregister.h b/src/qmlregister.h
index b2a86842a31881cd4ea48e6f770063ecbcc00d6d..02d8b35df4f6b0c48f4ffe7ed01f1ea620752dc8 100644
--- a/src/qmlregister.h
+++ b/src/qmlregister.h
@@ -30,6 +30,11 @@
 #define VER_MAJ      1
 #define VER_MIN      0
 
+class SystemTray;
+class LRCInstance;
+class AppSettingsManager;
+class ScreenInfo;
+
 // Hack for QtCreator autocomplete (part 1)
 // https://bugreports.qt.io/browse/QTCREATORBUG-20569
 namespace dummy {
@@ -53,5 +58,10 @@ Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
 // clang-format on
 
 namespace Utils {
-void registerTypes();
+void registerTypes(QQmlEngine* engine,
+                   SystemTray* systemTray,
+                   LRCInstance* lrcInstance,
+                   AppSettingsManager* appSettingsManager,
+                   ScreenInfo* screenInfo,
+                   QObject* parent);
 }
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 71d98352c05fd08b3f3963ae44a4ec1cd4c19d2c..f033d04acbd1457a667b86d1fc9616ac60ef44be 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -20,6 +20,7 @@ set(TESTS_INCLUDES
     ${CMAKE_SOURCE_DIR}/src
     ${CMAKE_SOURCE_DIR}/tests/qml
     ${CMAKE_SOURCE_DIR}/tests/unittests)
+set(TEST_QML_RESOURCES ${CMAKE_SOURCE_DIR}/tests/qml/resources.qrc)
 
 # Common jami files
 add_library(test_common_obj OBJECT ${COMMON_SOURCES} ${COMMON_HEADERS})
@@ -33,6 +34,7 @@ add_executable(qml_tests
                ${CMAKE_SOURCE_DIR}/tests/qml/main.cpp
                ${QML_RESOURCES}
                ${QML_RESOURCES_QML}
+               ${TEST_QML_RESOURCES}
                $<TARGET_OBJECTS:test_common_obj>)
 
 target_link_libraries(qml_tests
diff --git a/tests/qml/main.cpp b/tests/qml/main.cpp
index d32653cedce5fb7a17a82151e3dc68256ede5a83..671a5b58f71369f6c5b4f8acc72d53807ef3f2f7 100644
--- a/tests/qml/main.cpp
+++ b/tests/qml/main.cpp
@@ -22,20 +22,6 @@
 #include "appsettingsmanager.h"
 #include "connectivitymonitor.h"
 #include "systemtray.h"
-#include "namedirectory.h"
-#include "qrimageprovider.h"
-#include "tintedbuttonimageprovider.h"
-#include "avatarimageprovider.h"
-
-#include "accountadapter.h"
-#include "avadapter.h"
-#include "calladapter.h"
-#include "contactadapter.h"
-#include "pluginadapter.h"
-#include "messagesadapter.h"
-#include "settingsadapter.h"
-#include "utilsadapter.h"
-#include "conversationsadapter.h"
 
 #include <atomic>
 
@@ -43,6 +29,7 @@
 #include <QtQuickTest/quicktest.h>
 #include <QQmlEngine>
 #include <QQmlContext>
+#include <QtWebEngine>
 
 #ifdef Q_OS_WIN
 #include <windows.h>
@@ -67,11 +54,12 @@ public:
         settingsManager_.reset(new AppSettingsManager(this));
         systemTray_.reset(new SystemTray(settingsManager_.get(), this));
 
+        QFontDatabase::addApplicationFont(":/images/FontAwesome.otf");
+
 #if defined _MSC_VER && !COMPILE_ONLY
         gnutls_global_init();
 #endif
 
-        std::atomic_bool isMigrating(false);
         lrcInstance_.reset(
             new LRCInstance(nullptr, nullptr, "", connectivityMonitor_.get(), muteDring_));
         lrcInstance_->subscribeToDebugReceived();
@@ -82,56 +70,13 @@ public:
 
     void qmlEngineRegistration(QQmlEngine* engine)
     {
-        // setup the adapters (their lifetimes are that of MainApplication)
-        auto callAdapter = new CallAdapter(systemTray_.get(), lrcInstance_.data(), this);
-        auto messagesAdapter = new MessagesAdapter(settingsManager_.get(),
-                                                   lrcInstance_.data(),
-                                                   this);
-        auto conversationsAdapter = new ConversationsAdapter(systemTray_.get(),
-                                                             lrcInstance_.data(),
-                                                             this);
-        auto avAdapter = new AvAdapter(lrcInstance_.data(), this);
-        auto contactAdapter = new ContactAdapter(lrcInstance_.data(), this);
-        auto accountAdapter = new AccountAdapter(settingsManager_.get(), lrcInstance_.data(), this);
-        auto utilsAdapter = new UtilsAdapter(systemTray_.get(), lrcInstance_.data(), this);
-        auto settingsAdapter = new SettingsAdapter(settingsManager_.get(),
-                                                   lrcInstance_.data(),
-                                                   this);
-        auto pluginAdapter = new PluginAdapter(lrcInstance_.data(), this);
-
-        // qml adapter registration
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, callAdapter, "CallAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, messagesAdapter, "MessagesAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, conversationsAdapter, "ConversationsAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, avAdapter, "AvAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, contactAdapter, "ContactAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, accountAdapter, "AccountAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, utilsAdapter, "UtilsAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, settingsAdapter, "SettingsAdapter");
-        QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
-
-        // TODO: remove these
-        QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, AVModel, &lrcInstance_->avModel())
-        QML_REGISTERSINGLETONTYPE_CUSTOM(NS_MODELS, PluginModel, &lrcInstance_->pluginModel())
-        QML_REGISTERSINGLETONTYPE_CUSTOM(NS_HELPERS, UpdateManager, lrcInstance_->getUpdateManager())
-
-        // register other types that don't require injection(e.g. uncreatables, c++/qml singletons)
-        Utils::registerTypes();
-
-        engine->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance_.get()));
-        engine->addImageProvider(QLatin1String("tintedPixmap"),
-                                 new TintedButtonImageProvider(lrcInstance_.get()));
-        engine->addImageProvider(QLatin1String("avatarImage"),
-                                 new AvatarImageProvider(lrcInstance_.get()));
-
-        engine->rootContext()->setContextProperty("ScreenInfo", &screenInfo_);
-        engine->rootContext()->setContextProperty("LRCInstance", lrcInstance_.get());
-
-        engine->setObjectOwnership(&lrcInstance_->avModel(), QQmlEngine::CppOwnership);
-        engine->setObjectOwnership(&lrcInstance_->pluginModel(), QQmlEngine::CppOwnership);
-        engine->setObjectOwnership(lrcInstance_->getUpdateManager(), QQmlEngine::CppOwnership);
-        engine->setObjectOwnership(lrcInstance_.get(), QQmlEngine::CppOwnership);
-        engine->setObjectOwnership(&NameDirectory::instance(), QQmlEngine::CppOwnership);
+        // Expose custom types to the QML engine.
+        Utils::registerTypes(engine,
+                             systemTray_.get(),
+                             lrcInstance_.get(),
+                             settingsManager_.get(),
+                             &screenInfo_,
+                             this);
     }
 
 public Q_SLOTS:
@@ -156,7 +101,6 @@ public Q_SLOTS:
      */
     void qmlEngineAvailable(QQmlEngine* engine)
     {
-        engine->addImportPath("qrc:/tests/qml");
         qmlEngineRegistration(engine);
     }
 
@@ -196,6 +140,7 @@ main(int argc, char** argv)
     }
 
     QStandardPaths::setTestModeEnabled(true);
+    QtWebEngine::initialize();
 
     QTEST_SET_MAIN_SOURCE_PATH
     Setup setup(muteDring);
diff --git a/tests/qml/resources.qrc b/tests/qml/resources.qrc
new file mode 100644
index 0000000000000000000000000000000000000000..4d1385ab826e746d807d2a6458d23581001b8c94
--- /dev/null
+++ b/tests/qml/resources.qrc
@@ -0,0 +1,11 @@
+<RCC>
+    <qresource prefix="/">
+        <file>src/tst_LocalAccount.qml</file>
+        <file>src/tst_PresenceIndicator.qml</file>
+        <file>src/tst_MessageWebViewFooter.qml</file>
+        <file>src/resources/gif_test.gif</file>
+        <file>src/resources/gz_test.gz</file>
+        <file>src/resources/png_test.png</file>
+        <file>src/tst_FilesToSendContainer.qml</file>
+    </qresource>
+</RCC>
diff --git a/tests/qml/src/resources/gif_test.gif b/tests/qml/src/resources/gif_test.gif
new file mode 100644
index 0000000000000000000000000000000000000000..63ff54ade1e44ba428974b7499eff56c38f1db58
Binary files /dev/null and b/tests/qml/src/resources/gif_test.gif differ
diff --git a/tests/qml/src/resources/gz_test.gz b/tests/qml/src/resources/gz_test.gz
new file mode 100644
index 0000000000000000000000000000000000000000..8b4c8049773fe094ffeb57e3ff1fc9c014c980fa
Binary files /dev/null and b/tests/qml/src/resources/gz_test.gz differ
diff --git a/tests/qml/src/resources/png_test.png b/tests/qml/src/resources/png_test.png
new file mode 100644
index 0000000000000000000000000000000000000000..aef46d27351a67bf8b10d63bc759c6a85387d41c
Binary files /dev/null and b/tests/qml/src/resources/png_test.png differ
diff --git a/tests/qml/src/tst_FilesToSendContainer.qml b/tests/qml/src/tst_FilesToSendContainer.qml
new file mode 100644
index 0000000000000000000000000000000000000000..a0f0c6bfa46b1fdfb875013a69f9274bc3098cc8
--- /dev/null
+++ b/tests/qml/src/tst_FilesToSendContainer.qml
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * 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; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+
+import QtTest 1.2
+
+import net.jami.Models 1.0
+import net.jami.Constants 1.0
+
+import "qrc:/src/mainview/components"
+import"../../../src/mainview/components"
+
+ColumnLayout {
+    id: root
+
+    spacing: 0
+
+    width: 300
+    height: 300
+
+    FilesToSendContainer {
+        id: uut
+
+        Layout.alignment: Qt.AlignHCenter
+        Layout.preferredWidth: root.width
+        Layout.maximumWidth: JamiTheme.messageWebViewFooterContentMaximumWidth
+        Layout.preferredHeight: filesToSendCount ?
+                                    JamiTheme.messageWebViewFooterFileContainerPreferredHeight : 0
+
+        TestCase {
+            name: "FilesToSendContainer add/remove file test"
+            when: windowShown
+
+            function test_add_remove_file_test() {
+                // Add animated image file
+                uut.filesToSendListModel.addToPending(":/src/resources/gif_test.gif")
+                compare(uut.filesToSendCount, 1)
+
+                // Add image file
+                uut.filesToSendListModel.addToPending(":/src/resources/png_test.png")
+                compare(uut.filesToSendCount, 2)
+
+                // Add normal file
+                uut.filesToSendListModel.addToPending(":/src/resources/gz_test.gz")
+                compare(uut.filesToSendCount, 3)
+
+                // Flush
+                uut.filesToSendListModel.flush()
+                compare(uut.filesToSendCount, 0)
+            }
+        }
+    }
+}
diff --git a/tests/qml/src/tst_MessageWebViewFooter.qml b/tests/qml/src/tst_MessageWebViewFooter.qml
new file mode 100644
index 0000000000000000000000000000000000000000..f05ec45847c153a153596ce6be528e0d5b20cebc
--- /dev/null
+++ b/tests/qml/src/tst_MessageWebViewFooter.qml
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * 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; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+
+import QtTest 1.2
+
+import net.jami.Models 1.0
+import net.jami.Constants 1.0
+
+import "qrc:/src/mainview/components"
+import"../../../src/mainview/components"
+
+ColumnLayout {
+    id: root
+
+    spacing: 0
+
+    width: 300
+    height: uut.implicitHeight
+
+    MessageWebViewFooter {
+        id: uut
+
+        Layout.alignment: Qt.AlignHCenter
+        Layout.fillWidth: true
+        Layout.preferredHeight: implicitHeight
+        Layout.maximumHeight: JamiTheme.messageWebViewFooterMaximumHeight
+
+        TestCase {
+            name: "MessageWebViewFooter Send Message Button Visibility Test"
+            when: windowShown
+
+            function test_send_message_button_visibility() {
+                var filesToSendContainer = findChild(uut, "dataTransferSendContainer")
+                var sendMessageButton = findChild(uut, "sendMessageButton")
+                var messageBarTextArea = findChild(uut, "messageBarTextArea")
+
+                compare(sendMessageButton.visible, false)
+
+                // Text in messageBarTextArea will cause sendMessageButton to show
+                messageBarTextArea.insertText("test")
+                compare(sendMessageButton.visible, true)
+
+                // Text cleared in messageBarTextArea will cause sendMessageButton to hide
+                messageBarTextArea.clearText()
+                compare(sendMessageButton.visible, false)
+
+                // File added into filesToSendContainer will cause sendMessageButton to show
+                filesToSendContainer.filesToSendListModel.addToPending(
+                            ":/src/resources/png_test.png")
+                compare(filesToSendContainer.filesToSendCount, 1)
+                compare(sendMessageButton.visible, true)
+
+                // Files cleared from filesToSendContainer will cause sendMessageButton to hide
+                filesToSendContainer.filesToSendListModel.flush()
+                compare(filesToSendContainer.filesToSendCount, 0)
+                compare(sendMessageButton.visible, false)
+
+                // When the text and files both exist,
+                // clear one of them will still make sendMessageButton to show
+                messageBarTextArea.insertText("test")
+                filesToSendContainer.filesToSendListModel.addToPending(
+                            ":/src/resources/png_test.png")
+                messageBarTextArea.clearText()
+                compare(sendMessageButton.visible, true)
+
+                messageBarTextArea.insertText("test")
+                filesToSendContainer.filesToSendListModel.flush()
+                compare(sendMessageButton.visible, true)
+
+                // Both are cleared
+                messageBarTextArea.clearText()
+                compare(sendMessageButton.visible, false)
+            }
+        }
+    }
+}