diff --git a/src/app/htmlparser.h b/src/app/htmlparser.h
index 0b35385e19a3c8519f3e1478fdbefd4213067ae8..76f636e0e875a8759dce1b0dd8c1919b631a1a2f 100644
--- a/src/app/htmlparser.h
+++ b/src/app/htmlparser.h
@@ -49,7 +49,7 @@ public:
 
     bool parseHtmlString(const QString& html)
     {
-        return tidyParseString(doc_, html.toLocal8Bit().data()) >= 0;
+        return tidyParseString(doc_, html.toUtf8().data()) >= 0;
     }
 
     using TagNodeList = QMap<TidyTagId, QList<TidyNode>>;
@@ -86,11 +86,14 @@ public:
     // Extract the text value from a node.
     QString getNodeText(TidyNode node)
     {
-        TidyBuffer nodeValue = {};
+        TidyBuffer nodeValue = {0};
         if (!node || tidyNodeGetText(doc_, node, &nodeValue) != yes) {
             return QString();
         }
-        QString result = QString::fromUtf8((char*) nodeValue.bp, nodeValue.size);
+        QString result;
+        if (nodeValue.bp && nodeValue.size > 0) {
+            result = QString::fromUtf8(reinterpret_cast<char*>(nodeValue.bp), nodeValue.size);
+        }
         tidyBufFree(&nodeValue);
         return result;
     }
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d50908cfafa38450f72e40d1c049793e23b7d935..bc68fcf0363310c77e4224cde866c913de1f7636 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -8,6 +8,7 @@ set(QT_TESTING_MODULES
     QuickTest
     Test
     Widgets
+    HttpServer
 )
 find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED ${QT_TESTING_MODULES})
 foreach(MODULE ${QT_TESTING_MODULES})
@@ -84,6 +85,7 @@ set(UNIT_TESTS_SOURCE_FILES
     ${CMAKE_SOURCE_DIR}/tests/unittests/account_unittest.cpp
     ${CMAKE_SOURCE_DIR}/tests/unittests/contact_unittest.cpp
     ${CMAKE_SOURCE_DIR}/tests/unittests/messageparser_unittest.cpp
+    ${CMAKE_SOURCE_DIR}/tests/unittests/previewengine_unittest.cpp
     ${CMAKE_SOURCE_DIR}/tests/unittests/globaltestenvironment.h
     ${COMMON_TESTS_SOURCES})
 
diff --git a/tests/unittests/previewengine_unittest.cpp b/tests/unittests/previewengine_unittest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..70808dfd285d8feed76fdafdd7a0d119e8632748
--- /dev/null
+++ b/tests/unittests/previewengine_unittest.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 Savoir-faire Linux Inc.
+ *
+ * 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/>.
+ */
+
+#include "globaltestenvironment.h"
+
+#include <QtHttpServer/QHttpServer>
+
+class PreviewEngineFixture : public ::testing::Test
+{
+public:
+    // Prepare unit test context. Called at
+    // prior each unit test execution
+    void SetUp() override {
+        server = new QHttpServer();
+        // Setup a server that can return an HTML body.
+        server->listen(QHostAddress::LocalHost, 8000);
+    }
+
+   // Close unit test context. Called
+   // after each unit test ending
+    void TearDown() override {
+        delete server;
+    }
+
+    // An instance of QHttpServer used to create a server.
+    QHttpServer* server;
+};
+
+/*!
+ * WHEN  We parse a link
+ * THEN  The infoReady signal should be emitted once with the correct info
+ */
+TEST_F(PreviewEngineFixture, ParsingALinkEmitsInfoReadySignal)
+{
+    auto link = QString("http://localhost:8000/test");
+    server->route("/test", [] () {
+        return QString("<meta property=\"og:title\" content=\"Test title\">");
+    });
+
+    QSignalSpy infoReadySpy(globalEnv.previewEngine.data(), &PreviewEngine::infoReady);
+
+    Q_EMIT globalEnv.previewEngine->parseLink("msgId_01", link);
+
+    // Wait for the infoReady signal which should be emitted once with the correct ID.
+    infoReadySpy.wait();
+    EXPECT_EQ(infoReadySpy.count(), 1) << "infoReady signal should be emitted once";
+
+    QList<QVariant> infoReadyArguments = infoReadySpy.takeFirst();
+    EXPECT_TRUE(infoReadyArguments.at(0).typeId() == qMetaTypeId<QString>());
+    EXPECT_EQ(infoReadyArguments.at(0).toString(), "msgId_01");
+}
+
+/*!
+ * WHEN  We parse a link that has a description containing characters encoded using UTF-8
+ * THEN  The description should be parsed and match the original string
+ */
+TEST_F(PreviewEngineFixture, UTF8CharactersAreParsedCorrectly)
+{
+    auto link = QString("http://localhost:8000/test");
+    server->route("/test", [] () {
+        return QString("<meta property=\"og:description\" content=\"Test de caractères Utf-8");
+    });
+
+    QSignalSpy infoReadySpy(globalEnv.previewEngine.data(), &PreviewEngine::infoReady);
+
+    Q_EMIT globalEnv.previewEngine->parseLink("msgId_01", link);
+
+    // Wait for the infoReady signal which should be emitted once.
+    infoReadySpy.wait();
+    EXPECT_EQ(infoReadySpy.count(), 1) << "infoReady signal should be emitted once";
+
+    QList<QVariant> infoReadyArguments = infoReadySpy.takeFirst();
+    EXPECT_TRUE(infoReadyArguments.at(1).typeId() == qMetaTypeId<QVariantMap>());
+
+    // Check that the description is parsed correctly.
+    QVariantMap info = infoReadyArguments.at(1).toMap();
+    EXPECT_TRUE(info.contains("description"));
+    EXPECT_EQ(info["description"].toString(), "Test de caractères Utf-8");
+}