diff --git a/.gitignore b/.gitignore
index cfdc18e632fbbde1d211da73aeb9554fcb2faeb7..c067bf1a714e24661d8bc8e3a343e022e1d7663c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,9 @@ install/
 *.log
 *.pid
 
+# tests
+Testing/
+
 # auto-gen files
 src/app/resources.qrc
 src/app/qml.qrc
diff --git a/extras/scripts/build-windows.py b/extras/scripts/build-windows.py
index 341e6b9f4fee796084d3018cab7784381b804bf9..f12ac20182fbd6035d2401982284e1e1b9b8ec85 100644
--- a/extras/scripts/build-windows.py
+++ b/extras/scripts/build-windows.py
@@ -376,11 +376,19 @@ def run_tests(config_str, qt_dir):
         qt_dir, 'bin', 'QtWebEngineProcess.exe')
     os.environ["QML2_IMPORT_PATH"] = os.path.join(qt_dir, "qml")
 
+    cmd = ["ctest", "-V", "-C", config_str]
+    # On Windows, when running on a jenkins slave, the QML tests don't output
+    # anything to stdout/stderr. Workaround by outputting to a file and then
+    # printing the contents of the file.
+    if os.environ.get("JENKINS_URL"):
+        cmd += ["--output-log", "test.log", "--quiet"]
     tests_dir = os.path.join(build_dir, "tests")
-    if execute_cmd(["ctest", "-V", "-C", config_str],
-                   False, None, tests_dir):
-        print("Tests failed.")
-        sys.exit(1)
+    exit_code = execute_cmd(cmd, False, None, tests_dir)
+    # Print the contents of the log file.
+    if os.environ.get("JENKINS_URL"):
+        with open(os.path.join(tests_dir, "test.log"), "r") as file:
+            print(file.read())
+    sys.exit(exit_code)
 
 
 def generate_msi(version):
diff --git a/src/app/commoncomponents/EmojiReactionPopup.qml b/src/app/commoncomponents/EmojiReactionPopup.qml
index e7c76600f9a685b4cbdd4973d84953db2ce69d40..cc2dfbb3fbe2a63c6885efb805ec9028ad583ddc 100644
--- a/src/app/commoncomponents/EmojiReactionPopup.qml
+++ b/src/app/commoncomponents/EmojiReactionPopup.qml
@@ -31,7 +31,7 @@ Popup {
     background.visible: false
     parent: Overlay.overlay
 
-    property var emojiReaction
+    property var reactions
     property string msgId
 
     // center in parent
@@ -88,9 +88,9 @@ Popup {
                 spacing: 15
                 Layout.preferredWidth: 400
                 Layout.preferredHeight: childrenRect.height + 30 < 700 ? childrenRect.height + 30 : 700
-                model: Object.entries(emojiReaction)
+                model: Object.entries(reactions)
                 clip: true
-                property int modelCount: Object.entries(emojiReaction).length
+                property int modelCount: Object.entries(reactions).length
 
                 delegate: RowLayout {
                     width: parent.width
diff --git a/src/app/commoncomponents/EmojiReactions.qml b/src/app/commoncomponents/EmojiReactions.qml
index 032d08e44d227083b9221d6257375d169a591eb5..4ed741f52cd393e35311e22d7826b612cb72e679 100644
--- a/src/app/commoncomponents/EmojiReactions.qml
+++ b/src/app/commoncomponents/EmojiReactions.qml
@@ -24,19 +24,20 @@ import net.jami.Constants 1.1
 Item {
     id: root
 
-    property var emojiReaction
+    property var reactions
     property real contentHeight: bubble.height
     property real contentWidth: bubble.width
-    property var emojiTexts: ownEmojiList
 
     visible: emojis.length && Body !== ""
 
     property string emojis: {
+        if (reactions === undefined)
+            return [];
         var space = "";
         var emojiList = [];
         var emojiNumberList = [];
-        for (const reactions of Object.entries(emojiReaction)) {
-            var authorEmojiList = reactions[1];
+        for (const reaction of Object.entries(reactions)) {
+            var authorEmojiList = reaction[1];
             for (var emojiIndex in authorEmojiList) {
                 var emoji = authorEmojiList[emojiIndex];
                 if (emojiList.includes(emoji)) {
@@ -60,12 +61,14 @@ Item {
         return cur;
     }
 
-    property var ownEmojiList: {
+    property var ownEmojis: {
+        if (reactions === undefined)
+            return [];
         var list = [];
         var index = 0;
-        for (const reactions of Object.entries(emojiReaction)) {
-            var authorUri = reactions[0];
-            var authorEmojiList = reactions[1];
+        for (const reaction of Object.entries(reactions)) {
+            var authorUri = reaction[0];
+            var authorEmojiList = reaction[1];
             if (CurrentAccount.uri === authorUri) {
                 for (var emojiIndex in authorEmojiList) {
                     list[index] = authorEmojiList[emojiIndex];
diff --git a/src/app/commoncomponents/MessageOptionsPopup.qml b/src/app/commoncomponents/MessageOptionsPopup.qml
index 8e1b05da50832e58222ed0ef39212aa18bfc9f14..9a32d41a5e19b69a64f2f2a8f4541f15829f7717 100644
--- a/src/app/commoncomponents/MessageOptionsPopup.qml
+++ b/src/app/commoncomponents/MessageOptionsPopup.qml
@@ -33,9 +33,11 @@ Popup {
     padding: 0
     background.visible: false
 
+    required property var emojiReactions
+    property var emojiReplied: emojiReactions.ownEmojis
+
     required property string msgId
     required property string msgBody
-    required property var emojiReplied
     required property bool isOutgoing
     required property int type
     required property string transferName
@@ -107,27 +109,11 @@ Popup {
     onClosed: if (emojiPicker) emojiPicker.closeEmojiPicker()
 
     function getModel() {
-        var model = ["👍", "👎", "😂"]
-        var cur = []
-        //Add emoji reacted
-        var index = 0
-        for (let emoji of emojiReplied) {
-            if (index < model.length) {
-                cur[index] = emoji
-                index ++
-            }
-        }
-        //complete with default model
-        var modelIndex = cur.length
-        for (let j = 0; j < model.length; j++) {
-            if (cur.length < model.length) {
-                if (!cur.includes(model[j]) ) {
-                    cur[modelIndex] = model[j]
-                    modelIndex ++
-                }
-            }
-        }
-        return cur
+        const defaultModel = ["👍", "👎", "😂"]
+        const reactedEmojis = Array.isArray(emojiReplied) ? emojiReplied.slice(0, defaultModel.length) : []
+        const uniqueEmojis = Array.from(new Set(reactedEmojis))
+        const missingEmojis = defaultModel.filter(emoji => !uniqueEmojis.includes(emoji))
+        return uniqueEmojis.concat(missingEmojis)
     }
 
     Rectangle {
@@ -167,7 +153,7 @@ Popup {
 
                         background: Rectangle {
                             anchors.fill: parent
-                            opacity: emojiReplied.includes(modelData) ? 1 : 0
+                            opacity: emojiReplied ? (emojiReplied.includes(modelData) ? 1 : 0) : 0
                             color: JamiTheme.emojiReactPushButtonColor
                             radius: 10
                         }
diff --git a/src/app/commoncomponents/SBSMessageBase.qml b/src/app/commoncomponents/SBSMessageBase.qml
index 8fc642c92b51b24ba8b2342b97cfeeb71b275323..88988c086eead355b0db274414416cc1da2a3ce1 100644
--- a/src/app/commoncomponents/SBSMessageBase.qml
+++ b/src/app/commoncomponents/SBSMessageBase.qml
@@ -15,12 +15,10 @@
  * 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
 import QtQuick.Controls
 import QtQuick.Layouts
 import Qt5Compat.GraphicalEffects
-
 import net.jami.Models 1.1
 import net.jami.Adapters 1.1
 import net.jami.Constants 1.1
@@ -64,9 +62,7 @@ Control {
 
     // If the ListView attached properties are not available,
     // then the root delegate is likely a Loader.
-    readonly property ListView listView: ListView.view ?
-                                             ListView.view :
-                                             parent.ListView.view
+    readonly property ListView listView: ListView.view ? ListView.view : parent.ListView.view
 
     rightPadding: hPadding
     leftPadding: hPadding
@@ -99,7 +95,7 @@ Control {
                 id: username
                 text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author)
                 font.bold: true
-                visible:(seq === MsgSeq.first || seq === MsgSeq.single) && !isOutgoing
+                visible: (seq === MsgSeq.first || seq === MsgSeq.single) && !isOutgoing
                 font.pixelSize: JamiTheme.usernameBlockFontSize
                 color: JamiTheme.chatviewUsernameColor
                 lineHeight: JamiTheme.usernameBlockLineHeight
@@ -108,7 +104,6 @@ Control {
             }
         }
 
-
         Item {
             id: replyItem
             property bool isSelf: ReplyToAuthor === CurrentAccount.uri
@@ -123,14 +118,15 @@ Control {
             Layout.leftMargin: isOutgoing ? undefined : JamiTheme.sbsMessageBaseReplyMargin
             Layout.rightMargin: !isOutgoing ? undefined : JamiTheme.sbsMessageBaseReplyMargin
 
-            transform: Translate { y: JamiTheme.sbsMessageBaseReplyBottomMargin }
-
+            transform: Translate {
+                y: JamiTheme.sbsMessageBaseReplyBottomMargin
+            }
 
             ColumnLayout {
                 width: parent.width
                 spacing: 2
 
-                RowLayout{
+                RowLayout {
                     id: replyToLayout
 
                     Layout.alignment: isOutgoing ? Qt.AlignRight : Qt.AlignLeft
@@ -155,8 +151,8 @@ Control {
                         showPresenceIndicator: false
                         imageId: {
                             if (replyItem.isSelf)
-                                return CurrentAccount.id
-                            return ReplyToAuthor
+                                return CurrentAccount.id;
+                            return ReplyToAuthor;
                         }
                         mode: replyItem.isSelf ? Avatar.Mode.Account : Avatar.Mode.Contact
                     }
@@ -179,11 +175,10 @@ Control {
                     color: replyItem.isSelf ? CurrentConversation.color : JamiTheme.messageInBgColor
                     radius: msgRadius
 
-                    Layout.preferredWidth: replyToRow.width + 2*JamiTheme.preferredMarginSize
-                    Layout.preferredHeight: replyToRow.height + 2*JamiTheme.preferredMarginSize
+                    Layout.preferredWidth: replyToRow.width + 2 * JamiTheme.preferredMarginSize
+                    Layout.preferredHeight: replyToRow.height + 2 * JamiTheme.preferredMarginSize
                     Layout.alignment: isOutgoing ? Qt.AlignRight : Qt.AlignLeft
 
-
                     // place actual content here
                     ReplyToRow {
                         id: replyToRow
@@ -194,8 +189,8 @@ Control {
                     MouseArea {
                         z: 2
                         anchors.fill: parent
-                        onClicked: function(mouse) {
-                            CurrentConversation.scrollToMsg(ReplyTo)
+                        onClicked: function (mouse) {
+                            CurrentConversation.scrollToMsg(ReplyTo);
                         }
                     }
                 }
@@ -211,7 +206,7 @@ Control {
             Item {
                 id: avatarBlock
 
-                Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hPadding/3
+                Layout.preferredWidth: isOutgoing ? 0 : avatar.width + hPadding / 3
                 Layout.preferredHeight: isOutgoing ? 0 : bubble.height
                 Avatar {
                     id: avatar
@@ -238,7 +233,7 @@ Control {
                     hoverEnabled: true
                     onClicked: function (mouse) {
                         if (root.hoveredLink) {
-                            MessagesAdapter.openUrl(root.hoveredLink)
+                            MessagesAdapter.openUrl(root.hoveredLink);
                         }
                     }
                     property bool bubbleHovered: containsMouse || textHovered
@@ -276,30 +271,24 @@ Control {
                         anchors.verticalCenter: parent.verticalCenter
                         anchors.right: isOutgoing ? optionButtonItem.right : undefined
                         anchors.left: !isOutgoing ? optionButtonItem.left : undefined
-                        visible: CurrentAccount.type !== Profile.Type.SIP && Body !== "" &&
-                                    (
-                                        bubbleArea.bubbleHovered
-                                        || hovered
-                                        || reply.hovered
-                                        || bgHandler.hovered
-                                    )
+                        visible: CurrentAccount.type !== Profile.Type.SIP && Body !== "" && (bubbleArea.bubbleHovered || hovered || reply.hovered || bgHandler.hovered)
                         source: JamiResources.more_vert_24dp_svg
                         width: optionButtonItem.width / 2
                         height: optionButtonItem.height
 
                         onClicked: {
-                            var component = Qt.createComponent("qrc:/commoncomponents/MessageOptionsPopup.qml")
+                            var component = Qt.createComponent("qrc:/commoncomponents/MessageOptionsPopup.qml");
                             var obj = component.createObject(bubble, {
-                                                                 "emojiReplied": Qt.binding(() => emojiReaction.emojiTexts),
-                                                                 "isOutgoing": isOutgoing,
-                                                                 "msgId": Id,
-                                                                 "msgBody": Body,
-                                                                 "type": Type,
-                                                                 "transferName": TransferName,
-                                                                 "msgBubble": bubble,
-                                                                 "listView": listView
-                                                             })
-                            obj.open()
+                                    "emojiReactions": emojiReactions,
+                                    "isOutgoing": isOutgoing,
+                                    "msgId": Id,
+                                    "msgBody": Body,
+                                    "type": Type,
+                                    "transferName": TransferName,
+                                    "msgBubble": bubble,
+                                    "listView": listView
+                                });
+                            obj.open();
                         }
                     }
 
@@ -315,17 +304,11 @@ Control {
                         anchors.verticalCenter: parent.verticalCenter
                         anchors.right: isOutgoing ? more.left : undefined
                         anchors.left: !isOutgoing ? more.right : undefined
-                        visible: CurrentAccount.type !== Profile.Type.SIP && Body !== "" &&
-                                    (
-                                        bubbleArea.bubbleHovered
-                                        || hovered
-                                        || more.hovered
-                                        || bgHandler.hovered
-                                    )
+                        visible: CurrentAccount.type !== Profile.Type.SIP && Body !== "" && (bubbleArea.bubbleHovered || hovered || more.hovered || bgHandler.hovered)
 
                         onClicked: {
-                            MessagesAdapter.editId = ""
-                            MessagesAdapter.replyToId = Id
+                            MessagesAdapter.editId = "";
+                            MessagesAdapter.replyToId = Id;
                         }
                     }
                 }
@@ -335,18 +318,18 @@ Control {
 
                     property bool isEdited: PreviousBodies.length !== 0
                     visible: !IsEmojiOnly
-                    z:-1
+                    z: -1
                     out: isOutgoing
                     type: seq
                     isReply: root.isReply
 
                     function getBaseColor() {
-                        var baseColor = isOutgoing ? CurrentConversation.color : JamiTheme.messageInBgColor
+                        var baseColor = isOutgoing ? CurrentConversation.color : JamiTheme.messageInBgColor;
                         if (Id === MessagesAdapter.replyToId || Id === MessagesAdapter.editId) {
                             // If we are replying to or editing the message
-                            return Qt.darker(baseColor, 1.5)
+                            return Qt.darker(baseColor, 1.5);
                         }
-                        return baseColor
+                        return baseColor;
                     }
 
                     color: getBaseColor()
@@ -358,7 +341,6 @@ Control {
                     height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0)
                 }
 
-
                 Rectangle {
                     id: bg
 
@@ -412,8 +394,8 @@ Control {
                     target: CurrentConversation
                     function onScrollTo(id) {
                         if (id !== root.id)
-                            return
-                        selectAnimation.start()
+                            return;
+                        selectAnimation.start();
                     }
                 }
             }
@@ -444,10 +426,10 @@ Control {
 
                     width: {
                         if (root.readers.length === 0)
-                            return 0
-                        var nbAvatars = root.readers.length
-                        var margin = JamiTheme.avatarReadReceiptSize / 3
-                        return nbAvatars * JamiTheme.avatarReadReceiptSize - (nbAvatars - 1) * margin
+                            return 0;
+                        var nbAvatars = root.readers.length;
+                        var margin = JamiTheme.avatarReadReceiptSize / 3;
+                        return nbAvatars * JamiTheme.avatarReadReceiptSize - (nbAvatars - 1) * margin;
                     }
                     height: JamiTheme.avatarReadReceiptSize
 
@@ -458,20 +440,20 @@ Control {
         }
 
         EmojiReactions {
-            id: emojiReaction
+            id: emojiReactions
 
             property bool isOutgoing: Author === CurrentAccount.uri
             Layout.alignment: isOutgoing ? Qt.AlignRight : Qt.AlignLeft
             Layout.rightMargin: isOutgoing ? status.width : undefined
             Layout.leftMargin: !isOutgoing ? avatarBlock.width : undefined
-            Layout.topMargin: - contentHeight/4
+            Layout.topMargin: -contentHeight / 4
             Layout.preferredHeight: contentHeight + 5
             Layout.preferredWidth: contentWidth
-            emojiReaction: Reactions
+            reactions: Reactions
 
             TapHandler {
                 onTapped: {
-                    reactionPopup.open()
+                    reactionPopup.open();
                 }
             }
         }
@@ -483,10 +465,10 @@ Control {
             orientation: ListView.Horizontal
             Layout.preferredHeight: {
                 if (showTime || seq === MsgSeq.last)
-                    return contentHeight + timestampItem.contentHeight
+                    return contentHeight + timestampItem.contentHeight;
                 else if (readsMultiple.visible)
-                    return JamiTheme.avatarReadReceiptSize
-                return 0
+                    return JamiTheme.avatarReadReceiptSize;
+                return 0;
             }
 
             ReadStatus {
@@ -494,14 +476,14 @@ Control {
                 visible: root.readers.length > 1 && CurrentAccount.sendReadReceipt
                 width: {
                     if (root.readers.length === 0)
-                        return 0
-                    var nbAvatars = root.readers.length
-                    var margin = JamiTheme.avatarReadReceiptSize / 3
-                    return nbAvatars * JamiTheme.avatarReadReceiptSize - (nbAvatars - 1) * margin
+                        return 0;
+                    var nbAvatars = root.readers.length;
+                    var margin = JamiTheme.avatarReadReceiptSize / 3;
+                    return nbAvatars * JamiTheme.avatarReadReceiptSize - (nbAvatars - 1) * margin;
                 }
 
                 anchors.right: parent.right
-                anchors.top : parent.top
+                anchors.top: parent.top
                 anchors.topMargin: 1
                 readers: root.readers
             }
@@ -511,7 +493,7 @@ Control {
     EmojiReactionPopup {
         id: reactionPopup
 
-        emojiReaction: Reactions
+        reactions: Reactions
         msgId: Id
     }
 }
diff --git a/tests/qml/resources.qrc b/tests/qml/resources.qrc
index 97f685ad3c8d07ddc291f4278a85e7dff2be1bf0..969b3a28cca7737057e4e252a5c1db531b9b3124 100644
--- a/tests/qml/resources.qrc
+++ b/tests/qml/resources.qrc
@@ -3,6 +3,7 @@
         <file>src/tst_LocalAccount.qml</file>
         <file>src/tst_WizardView.qml</file>
         <file>src/tst_NewSwarmPage.qml</file>
+        <file>src/tst_MessageOptions.qml</file>
         <file>src/resources/gif_test.gif</file>
         <file>src/resources/gz_test.gz</file>
         <file>src/resources/png_test.png</file>
diff --git a/tests/qml/src/tst_MessageOptions.qml b/tests/qml/src/tst_MessageOptions.qml
new file mode 100644
index 0000000000000000000000000000000000000000..4fb25db379a3d7c8cd824034efbaefd1a456e1ba
--- /dev/null
+++ b/tests/qml/src/tst_MessageOptions.qml
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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/>.
+ */
+
+import QtQuick
+import QtTest
+
+import net.jami.Adapters 1.1
+import net.jami.Models 1.1
+import net.jami.Constants 1.1
+import net.jami.Enums 1.1
+
+import "../../../src/app/"
+import "../../../src/app/commoncomponents"
+
+Item {
+    id: uut
+
+    // Mock reactions
+    EmojiReactions {
+        id: emojiReactions
+    }
+
+    // Mock bubble item
+    Item {
+        id: bubble
+    }
+
+    // Mock listview
+    JamiListView {
+        id: listView
+    }
+
+    property int id
+    function getId() {
+        id += 1;
+        return "test" + id;
+    }
+
+    function getOptionsPopup(isOutgoing, id, body, type, transferName) {
+        var component = Qt.createComponent("qrc:/commoncomponents/MessageOptionsPopup.qml");
+        var obj = component.createObject(bubble, {
+                "emojiReactions": emojiReactions,
+                "isOutgoing": isOutgoing,
+                "msgId": id,
+                "msgBody": body,
+                "type": type,
+                "transferName": transferName,
+                "msgBubble": bubble,
+                "listView": listView
+            });
+        return obj;
+    }
+
+    SignalSpy {
+        id: accountAdded
+
+        target: AccountAdapter
+        signalName: "accountAdded"
+    }
+
+    TestCase {
+        name: "Test message options popup instantiation"
+        when: windowShown
+
+        function test_createMessageOptionsPopup() {
+            // Create an account and set it as current account
+            AccountAdapter.createSIPAccount({
+                "username": "currentAccountUsername"
+            });
+            // Block on account creation
+            accountAdded.wait(1000);
+
+            // Add some emoji reactions (one from current account uri, one from another uri)
+            emojiReactions.reactions = {
+                "currentAccountUsername": ["🌭"],
+                "notCurrentAccountUri": ["🌮"]
+            };
+
+            var optionsPopup = getOptionsPopup(true, getId(), "test", 0, "test");
+            verify(optionsPopup !== null, "Message options popup should be created");
+
+            // Check if the popup is visible once opened.
+            optionsPopup.open();
+            verify(optionsPopup.visible, "Message options popup should be visible");
+
+            // Check that emojiReplied has our emoji.
+            verify(JSON.stringify(optionsPopup.emojiReplied) === JSON.stringify(["🌭"]),
+                "Message options popup should have emoji replied");
+        }
+    }
+}