branch: elpa/jabber
commit f36fbf5a8b905c7d2cd9ca7ffed85d028c82e597
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    chatstates: Preserve typing on reaction fallbacks
---
 lisp/jabber-chatstates.el       | 14 ++++++--
 tests/jabber-test-chatstates.el | 75 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 3 deletions(-)

diff --git a/lisp/jabber-chatstates.el b/lisp/jabber-chatstates.el
index 8b1cc075d6..c631094fae 100644
--- a/lisp/jabber-chatstates.el
+++ b/lisp/jabber-chatstates.el
@@ -44,6 +44,7 @@
 
 (declare-function jabber-muc-find-buffer "jabber-muc" (group))
 (declare-function jabber-muc-nickname "jabber-muc" (group &optional jc))
+(declare-function jabber-reactions--reaction-only-p "jabber-reactions" 
(xml-data))
 
 (defgroup jabber-chatstates nil
   "Chat state notifications."
@@ -287,6 +288,12 @@ Added to `kill-buffer-hook' in chat buffers."
 
 ;;; COMMON
 
+(defun jabber-chatstates--real-body-message-p (xml-data)
+  "Return non-nil when XML-DATA has a body that should clear chatstates."
+  (and (jabber-xml-get-children xml-data 'body)
+       (not (and (fboundp 'jabber-reactions--reaction-only-p)
+                 (jabber-reactions--reaction-only-p xml-data)))))
+
 (defun jabber-chatstates--handle-direct-state (jc xml-data from)
   "Update the direct chat buffer from XML-DATA sent by FROM on JC."
   (when-let* ((buffer (get-buffer (jabber-chat-get-buffer from jc))))
@@ -306,8 +313,9 @@ Added to `kill-buffer-hook' in chat buffers."
             (add-hook 'post-command-hook #'jabber-chatstates-after-change nil 
t)
             (add-hook 'kill-buffer-hook #'jabber-chatstates-send-gone nil t))
 
-          (setq jabber-chatstates-last-state state)
-          (jabber-chatstates--update-ewoc state)))))))
+          (when (or state (jabber-chatstates--real-body-message-p xml-data))
+            (setq jabber-chatstates-last-state state)
+            (jabber-chatstates--update-ewoc state))))))))
 
 (defun jabber-handle-incoming-message-chatstates (jc xml-data)
   "Update the chat buffer's typing indicator from XML-DATA on JC."
@@ -315,7 +323,7 @@ Added to `kill-buffer-hook' in chat buffers."
     (if (string= (jabber-xml-get-attribute xml-data 'type) "groupchat")
         (let ((state (jabber-chatstates--message-state xml-data)))
           (when (and (not (string= (jabber-xml-get-attribute xml-data 'type) 
"error"))
-                     (or state (jabber-xml-get-children xml-data 'body)))
+                     (or state (jabber-chatstates--real-body-message-p 
xml-data)))
             (jabber-chatstates--handle-muc-state jc from state)))
       (jabber-chatstates--handle-direct-state jc xml-data from))))
 
diff --git a/tests/jabber-test-chatstates.el b/tests/jabber-test-chatstates.el
index 4146fe14a0..58b2c137d3 100644
--- a/tests/jabber-test-chatstates.el
+++ b/tests/jabber-test-chatstates.el
@@ -8,6 +8,7 @@
 
 (require 'ert)
 (require 'jabber-chatstates)
+(require 'jabber-reactions)
 
 (defun jabber-test-chatstates--message (from type state)
   "Return a message sexp from FROM with TYPE and chat STATE."
@@ -29,6 +30,18 @@
                         (id . "target-1"))
                        (reaction nil "šŸ‘"))))
 
+(defun jabber-test-chatstates--reaction-fallback-message (from type)
+  "Return a reaction message from FROM with TYPE and fallback text."
+  `(message ((from . ,from)
+             (type . ,type))
+            (body nil "> quoted\nšŸ‘")
+            (reactions ((xmlns . "urn:xmpp:reactions:0")
+                        (id . "target-1"))
+                       (reaction nil "šŸ‘"))
+            (fallback ((xmlns . "urn:xmpp:fallback:0")
+                       (for . "urn:xmpp:reactions:0"))
+                      (body ((start . "0") (end . "10"))))))
+
 (defun jabber-test-chatstates--ewoc-data ()
   "Return the current EWOC data in display order."
   (let (data)
@@ -438,6 +451,26 @@ nil after the first message, breaking subsequent composing 
detection."
         (should (equal (jabber-test-chatstates--ewoc-data)
                        '((:typing "alice is typing..."))))))))
 
+(ert-deftest 
jabber-test-chatstates-groupchat-reaction-fallback-preserves-composing ()
+  "Incoming groupchat reaction fallback body does not clear occupant typing."
+  (with-temp-buffer
+    (let ((muc-buffer (current-buffer))
+          (jabber-chat-ewoc (ewoc-create #'ignore)))
+      (cl-letf (((symbol-function 'jabber-muc-find-buffer)
+                 (lambda (_group) muc-buffer))
+                ((symbol-function 'jabber-muc-nickname) #'ignore))
+        (jabber-handle-incoming-message-chatstates
+         'fake-jc
+         (jabber-test-chatstates--message
+          "[email protected]/alice" "groupchat" 'composing))
+        (jabber-handle-incoming-message-chatstates
+         'fake-jc
+         (jabber-test-chatstates--reaction-fallback-message
+          "[email protected]/alice" "groupchat"))
+        (should (equal jabber-chatstates--muc-composers '("alice")))
+        (should (equal (jabber-test-chatstates--ewoc-data)
+                       '((:typing "alice is typing..."))))))))
+
 (ert-deftest jabber-test-chatstates-groupchat-self-nick-is-ignored ()
   "Incoming groupchat state from our nick refreshes without mutating 
composers."
   (let ((find-called nil)
@@ -515,6 +548,48 @@ nil after the first message, breaking subsequent composing 
detection."
           (should (eq jabber-chatstates-last-state 'composing))
           (should (equal entered '(:typing "[email protected] is 
typing..."))))))))
 
+(ert-deftest 
jabber-test-chatstates-direct-reaction-fallback-preserves-composing ()
+  "Incoming direct reaction fallback body does not clear peer typing."
+  (with-temp-buffer
+    (rename-buffer " *jabber-direct-chatstates-reaction-fallback-test*" t)
+    (let ((chat-buffer (current-buffer))
+          (jabber-chat-ewoc (ewoc-create #'ignore)))
+      (setq-local jabber-chatting-with "[email protected]/resource")
+      (cl-letf (((symbol-function 'jabber-chat-get-buffer)
+                 (lambda (_from _jc) (buffer-name chat-buffer))))
+        (jabber-handle-incoming-message-chatstates
+         'fake-jc
+         (jabber-test-chatstates--message
+          "[email protected]/resource" "chat" 'composing))
+        (jabber-handle-incoming-message-chatstates
+         'fake-jc
+         (jabber-test-chatstates--reaction-fallback-message
+          "[email protected]/resource" "chat"))
+        (should (eq jabber-chatstates-last-state 'composing))
+        (should (equal (jabber-test-chatstates--ewoc-data)
+                       '((:typing "[email protected] is typing..."))))))))
+
+(ert-deftest jabber-test-chatstates-direct-message-clears-composing ()
+  "Incoming direct message without chatstate clears peer typing."
+  (with-temp-buffer
+    (rename-buffer " *jabber-direct-chatstates-message-clears-test*" t)
+    (let ((chat-buffer (current-buffer))
+          (jabber-chat-ewoc (ewoc-create #'ignore)))
+      (setq-local jabber-chatting-with "[email protected]/resource")
+      (cl-letf (((symbol-function 'jabber-chat-get-buffer)
+                 (lambda (_from _jc) (buffer-name chat-buffer))))
+        (jabber-handle-incoming-message-chatstates
+         'fake-jc
+         (jabber-test-chatstates--message
+          "[email protected]/resource" "chat" 'composing))
+        (jabber-handle-incoming-message-chatstates
+         'fake-jc
+         (jabber-test-chatstates--plain-message
+          "[email protected]/resource" "chat"))
+        (should-not jabber-chatstates-last-state)
+        (should-not jabber-chatstates--ewoc-node)
+        (should-not (jabber-test-chatstates--ewoc-data))))))
+
 (ert-deftest jabber-test-chatstates-direct-active-forgets-stale-node ()
   "Incoming direct active clears a stale typing node without error."
   (with-temp-buffer

Reply via email to