More features
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
;;; noise-conversation-test.el --- Tests for noise-conversation -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'ert)
|
||||
(require 'noise-conversation)
|
||||
|
||||
(defvar noise-conversation-test--db
|
||||
(expand-file-name "noise-conversation-test.db" temporary-file-directory))
|
||||
|
||||
(defmacro noise-conversation-test--with-db (&rest body)
|
||||
"Run BODY with a fresh test database and clean up conversation buffers."
|
||||
`(let ((noise-db-file noise-conversation-test--db))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(when (file-exists-p noise-db-file)
|
||||
(delete-file noise-db-file))
|
||||
(noise-db-init)
|
||||
,@body)
|
||||
(noise-db--close)
|
||||
(when (file-exists-p noise-conversation-test--db)
|
||||
(delete-file noise-conversation-test--db))
|
||||
(dolist (buf (buffer-list))
|
||||
(when (string-prefix-p "*signal:" (buffer-name buf))
|
||||
(kill-buffer buf))))))
|
||||
|
||||
(defun noise-conversation-test--seed ()
|
||||
"Insert a direct conversation with Sarah and two messages."
|
||||
(noise-db-upsert-contact "+1" :name "Sarah")
|
||||
(noise-db-upsert-conversation "+1" :type "direct" :name "Sarah"
|
||||
:members '("+1"))
|
||||
(noise-db-insert-message "+1" :timestamp 1717930000.0
|
||||
:source "Sarah" :body "are you coming?")
|
||||
(noise-db-insert-message "+1" :timestamp 1717930100.0
|
||||
:source "me" :body "yes, leaving now"
|
||||
:receipt-state "delivered"))
|
||||
|
||||
(ert-deftest noise-conversation-open-renders-history ()
|
||||
"Opening a conversation renders messages, a day separator, and the prompt."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(let ((text (buffer-substring-no-properties (point-min) (point-max))))
|
||||
(should (string-match-p "Sarah> are you coming\\?" text))
|
||||
(should (string-match-p "me> yes, leaving now" text))
|
||||
;; day separator present
|
||||
(should (string-match-p "─── .* ───" text))
|
||||
;; delivered receipt
|
||||
(should (string-match-p "✓✓" text))
|
||||
;; compose prompt at the bottom
|
||||
(should (string-match-p "^> " text))))))
|
||||
|
||||
(ert-deftest noise-conversation-open-marks-read ()
|
||||
"Opening a conversation resets its unread count."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(noise-db-upsert-conversation "+1" :unread-count 4)
|
||||
(noise-conversation-open "+1")
|
||||
(should (= 0 (gethash "unread_count" (noise-db-get-conversation "+1"))))))
|
||||
|
||||
(ert-deftest noise-conversation-history-is-read-only ()
|
||||
"Typing into the history region should fail; the prompt is editable."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(goto-char (point-min))
|
||||
(should-error (insert "x"))
|
||||
(goto-char (point-max))
|
||||
(insert "hello")
|
||||
(should (string= "hello" (noise-conversation--current-input))))))
|
||||
|
||||
(ert-deftest noise-conversation-send-stores-and-clears ()
|
||||
"Sending stores the message with the server timestamp and clears input."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(goto-char (point-max))
|
||||
(insert "on my way")
|
||||
(let (sent-params)
|
||||
(cl-letf (((symbol-function 'noise-rpc-call-for-account)
|
||||
(lambda (method &rest params)
|
||||
(should (string= "send" method))
|
||||
(setq sent-params params)
|
||||
(let ((ht (make-hash-table :test 'equal)))
|
||||
(puthash "timestamp" 1717930200123 ht)
|
||||
ht))))
|
||||
(noise-conversation-send))
|
||||
(should (equal ["+1"] (plist-get sent-params :recipient)))
|
||||
(should (string= "on my way" (plist-get sent-params :message))))
|
||||
;; input cleared, message rendered
|
||||
(should (string= "" (noise-conversation--current-input)))
|
||||
(let ((msgs (noise-db-get-messages "+1")))
|
||||
(should (= 3 (length msgs)))
|
||||
(let ((last (car (last msgs))))
|
||||
(should (string= "me" (gethash "source" last)))
|
||||
(should (string= "on my way" (gethash "body" last)))
|
||||
(should (string= "sent" (gethash "receipt_state" last)))
|
||||
(should (= 1717930200.123 (gethash "timestamp" last))))))))
|
||||
|
||||
(ert-deftest noise-conversation-send-group-uses-group-id ()
|
||||
"Sending in a group conversation uses the groupId parameter."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-db-upsert-conversation "grp1" :type "group" :name "cycling crew")
|
||||
(with-current-buffer (noise-conversation-open "grp1")
|
||||
(goto-char (point-max))
|
||||
(insert "saturday 7am?")
|
||||
(let (sent-params)
|
||||
(cl-letf (((symbol-function 'noise-rpc-call-for-account)
|
||||
(lambda (_method &rest params)
|
||||
(setq sent-params params)
|
||||
nil)))
|
||||
(noise-conversation-send))
|
||||
(should (string= "grp1" (plist-get sent-params :groupId)))
|
||||
(should-not (plist-get sent-params :recipient))))))
|
||||
|
||||
(ert-deftest noise-conversation-send-empty-does-nothing ()
|
||||
"Sending with no input should not call signal-cli."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(cl-letf (((symbol-function 'noise-rpc-call-for-account)
|
||||
(lambda (&rest _) (error "Should not be called"))))
|
||||
(noise-conversation-send))
|
||||
(should (= 2 (length (noise-db-get-messages "+1")))))))
|
||||
|
||||
(ert-deftest noise-conversation-render-preserves-input ()
|
||||
"Re-rendering keeps text already typed at the prompt."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(goto-char (point-max))
|
||||
(insert "draft text")
|
||||
(noise-conversation--render)
|
||||
(should (string= "draft text" (noise-conversation--current-input))))))
|
||||
|
||||
(ert-deftest noise-conversation-format-attachment ()
|
||||
"Attachment messages render the [attachment: ...] hint."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(noise-db-insert-message "+1" :timestamp 1717930300.0
|
||||
:source "Sarah" :body ""
|
||||
:has-attachment t
|
||||
:attachment-path "IMG_2041.jpg")
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(should (string-match-p "\\[attachment: IMG_2041\\.jpg — RET to view\\]"
|
||||
(buffer-string))))))
|
||||
|
||||
(ert-deftest noise-conversation-typing-indicator ()
|
||||
"Typing STARTED/STOPPED toggles the modeline indicator."
|
||||
(noise-conversation-test--with-db
|
||||
(noise-conversation-test--seed)
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(noise-conversation-set-typing "+1" "Sarah" "STARTED")
|
||||
(should (string= "Sarah" noise-conversation--typing))
|
||||
(should (string-match-p "Sarah is typing…" mode-line-format))
|
||||
(noise-conversation-set-typing "+1" "Sarah" "STOPPED")
|
||||
(should-not noise-conversation--typing))))
|
||||
+19
-10
@@ -28,7 +28,10 @@
|
||||
(progn
|
||||
(noise-new-chat-test--setup)
|
||||
,@body)
|
||||
(noise-new-chat-test--teardown))))
|
||||
(noise-new-chat-test--teardown)
|
||||
(dolist (buf (buffer-list))
|
||||
(when (string-prefix-p "*signal:" (buffer-name buf))
|
||||
(kill-buffer buf))))))
|
||||
|
||||
(defun noise-new-chat-test--contact (&rest kvs)
|
||||
"Build a hash table simulating a signal-cli contact from KVS pairs."
|
||||
@@ -135,13 +138,19 @@
|
||||
(noise-db-get-conversation-members "+1")))))))
|
||||
|
||||
(ert-deftest noise-new-chat-with-contact-is-idempotent ()
|
||||
"Starting a chat twice should not clobber the existing conversation."
|
||||
"Starting a chat twice should not clobber the existing conversation.
|
||||
Opening the conversation marks it read, but history metadata survives."
|
||||
(noise-new-chat-test--with-db
|
||||
(noise-db-upsert-conversation "+1" :type "direct" :name "Sarah"
|
||||
:members '("+1")
|
||||
:unread-count 3
|
||||
:last-message-preview "hello")
|
||||
(noise-new-chat-with-contact "+1" "Sarah")
|
||||
(let ((conv (noise-db-get-conversation "+1")))
|
||||
(should (= 3 (gethash "unread_count" conv)))
|
||||
(should (string= "hello" (gethash "last_message_preview" conv))))))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(noise-db-upsert-conversation "+1" :type "direct" :name "Sarah"
|
||||
:members '("+1")
|
||||
:unread-count 3
|
||||
:last-message-preview "hello")
|
||||
(noise-new-chat-with-contact "+1" "Sarah")
|
||||
(let ((conv (noise-db-get-conversation "+1")))
|
||||
;; opening the chat marks it read
|
||||
(should (= 0 (gethash "unread_count" conv)))
|
||||
(should (string= "hello" (gethash "last_message_preview" conv)))))
|
||||
(when (get-buffer "*signal:Sarah*")
|
||||
(kill-buffer "*signal:Sarah*")))))
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
;;; noise-receive-poll-test.el --- Tests for the receive poller -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'ert)
|
||||
(require 'noise-receive)
|
||||
|
||||
(ert-deftest noise-receive--poll-surfaces-error-once ()
|
||||
"A failing poll messages the user once, not on every retry."
|
||||
(let ((noise-receive--last-poll-error nil)
|
||||
(messages nil))
|
||||
(cl-letf (((symbol-function 'noise-receive)
|
||||
(lambda ()
|
||||
(signal 'noise-rpc-error
|
||||
'("signal-cli error -32602: Method requires valid account parameter"))))
|
||||
((symbol-function 'message)
|
||||
(lambda (fmt &rest args)
|
||||
(push (apply #'format fmt args) messages))))
|
||||
(noise-receive--poll)
|
||||
(noise-receive--poll))
|
||||
(should (= 1 (length messages)))
|
||||
(should (string-match-p "account parameter" (car messages)))
|
||||
;; the hint points at the fix
|
||||
(should (string-match-p "noise-select-account" (car messages)))))
|
||||
|
||||
(ert-deftest noise-receive--poll-recovers-and-resets ()
|
||||
"After a successful poll, a recurring error is shown again."
|
||||
(let ((noise-receive--last-poll-error nil)
|
||||
(messages nil)
|
||||
(fail t))
|
||||
(cl-letf (((symbol-function 'noise-receive)
|
||||
(lambda ()
|
||||
(when fail
|
||||
(signal 'noise-rpc-error '("connection refused")))
|
||||
0))
|
||||
((symbol-function 'message)
|
||||
(lambda (fmt &rest args)
|
||||
(push (apply #'format fmt args) messages))))
|
||||
(noise-receive--poll) ; error -> message
|
||||
(setq fail nil)
|
||||
(noise-receive--poll) ; success -> resets
|
||||
(setq fail t)
|
||||
(noise-receive--poll)) ; error again -> message again
|
||||
(should (= 2 (length messages)))))
|
||||
@@ -0,0 +1,191 @@
|
||||
;;; noise-receive-test.el --- Tests for noise-receive -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'ert)
|
||||
(require 'noise-receive)
|
||||
|
||||
(defvar noise-receive-test--db
|
||||
(expand-file-name "noise-receive-test.db" temporary-file-directory))
|
||||
|
||||
(defmacro noise-receive-test--with-db (&rest body)
|
||||
"Run BODY with a fresh test database and clean up conversation buffers."
|
||||
`(let ((noise-db-file noise-receive-test--db))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(when (file-exists-p noise-db-file)
|
||||
(delete-file noise-db-file))
|
||||
(noise-db-init)
|
||||
,@body)
|
||||
(noise-db--close)
|
||||
(when (file-exists-p noise-receive-test--db)
|
||||
(delete-file noise-receive-test--db))
|
||||
(dolist (buf (buffer-list))
|
||||
(when (string-prefix-p "*signal:" (buffer-name buf))
|
||||
(kill-buffer buf))))))
|
||||
|
||||
(defun noise-receive-test--ht (&rest kvs)
|
||||
"Build a hash table from KVS key/value pairs."
|
||||
(let ((ht (make-hash-table :test 'equal)))
|
||||
(while kvs
|
||||
(puthash (car kvs) (cadr kvs) ht)
|
||||
(setq kvs (cddr kvs)))
|
||||
ht))
|
||||
|
||||
(defun noise-receive-test--run (&rest envelopes)
|
||||
"Run `noise-receive' against mocked ENVELOPES, returning its result."
|
||||
(cl-letf (((symbol-function 'noise-rpc-call-for-account)
|
||||
(lambda (method &rest _)
|
||||
(should (string= "receive" method))
|
||||
(apply #'vector
|
||||
(mapcar (lambda (e)
|
||||
(noise-receive-test--ht "envelope" e))
|
||||
envelopes)))))
|
||||
(noise-receive)))
|
||||
|
||||
(ert-deftest noise-receive-direct-message ()
|
||||
"An incoming direct message creates contact, conversation, and message."
|
||||
(noise-receive-test--with-db
|
||||
(should (= 1 (noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+1" "sourceName" "Sarah"
|
||||
"dataMessage" (noise-receive-test--ht
|
||||
"timestamp" 1717930000000
|
||||
"message" "are you coming?")))))
|
||||
(should (string= "Sarah" (gethash "name" (noise-db-get-contact "+1"))))
|
||||
(let ((conv (noise-db-get-conversation "+1")))
|
||||
(should (string= "direct" (gethash "type" conv)))
|
||||
(should (string= "Sarah" (gethash "name" conv)))
|
||||
(should (= 1 (gethash "unread_count" conv)))
|
||||
;; direct chats have no sender prefix in the preview
|
||||
(should (string= "" (gethash "last_message_sender" conv)))
|
||||
(should (string= "are you coming?"
|
||||
(gethash "last_message_preview" conv))))
|
||||
(let ((msg (car (noise-db-get-messages "+1"))))
|
||||
(should (string= "Sarah" (gethash "source" msg)))
|
||||
(should (= 1717930000.0 (gethash "timestamp" msg))))))
|
||||
|
||||
(ert-deftest noise-receive-group-message ()
|
||||
"An incoming group message creates a group conversation keyed by groupId."
|
||||
(noise-receive-test--with-db
|
||||
(noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+2" "sourceName" "Priya"
|
||||
"dataMessage" (noise-receive-test--ht
|
||||
"timestamp" 1717930000000
|
||||
"message" "saturday 7am, usual spot?"
|
||||
"groupInfo" (noise-receive-test--ht
|
||||
"groupId" "grp1"
|
||||
"groupName" "cycling crew"))))
|
||||
(let ((conv (noise-db-get-conversation "grp1")))
|
||||
(should (string= "group" (gethash "type" conv)))
|
||||
(should (string= "cycling crew" (gethash "name" conv)))
|
||||
;; group previews keep the sender prefix
|
||||
(should (string= "Priya" (gethash "last_message_sender" conv))))))
|
||||
|
||||
(ert-deftest noise-receive-attachment-only-message ()
|
||||
"An attachment-only message is stored and gets a photo preview."
|
||||
(noise-receive-test--with-db
|
||||
(noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+1" "sourceName" "Sarah"
|
||||
"dataMessage" (noise-receive-test--ht
|
||||
"timestamp" 1717930000000
|
||||
"message" :null
|
||||
"attachments" (vector (noise-receive-test--ht
|
||||
"filename" "IMG_2041.jpg")))))
|
||||
(let ((msg (car (noise-db-get-messages "+1"))))
|
||||
(should (= 1 (gethash "has_attachment" msg)))
|
||||
(should (string= "IMG_2041.jpg" (gethash "attachment_path" msg))))
|
||||
(should (string= "photo: IMG_2041.jpg"
|
||||
(gethash "last_message_preview"
|
||||
(noise-db-get-conversation "+1"))))))
|
||||
|
||||
(ert-deftest noise-receive-sync-message ()
|
||||
"A sync sentMessage from another device is stored as from `me'."
|
||||
(noise-receive-test--with-db
|
||||
(noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+me"
|
||||
"syncMessage" (noise-receive-test--ht
|
||||
"sentMessage" (noise-receive-test--ht
|
||||
"timestamp" 1717930000000
|
||||
"destinationNumber" "+1"
|
||||
"message" "sent from my phone"))))
|
||||
(let ((msg (car (noise-db-get-messages "+1"))))
|
||||
(should (string= "me" (gethash "source" msg)))
|
||||
(should (string= "sent" (gethash "receipt_state" msg)))
|
||||
(should (string= "sent from my phone" (gethash "body" msg))))
|
||||
;; own messages don't bump unread
|
||||
(should (= 0 (gethash "unread_count" (noise-db-get-conversation "+1"))))))
|
||||
|
||||
(ert-deftest noise-receive-delivery-receipt ()
|
||||
"A delivery receipt upgrades the receipt state of the matching message."
|
||||
(noise-receive-test--with-db
|
||||
(noise-db-upsert-conversation "+1" :type "direct" :name "Sarah")
|
||||
(noise-db-insert-message "+1" :timestamp 1717930000.0
|
||||
:source "me" :body "hi" :receipt-state "sent")
|
||||
(noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+1"
|
||||
"receiptMessage" (noise-receive-test--ht
|
||||
"isDelivery" t "isRead" :false
|
||||
"timestamps" (vector 1717930000000))))
|
||||
(should (string= "delivered"
|
||||
(gethash "receipt_state"
|
||||
(car (noise-db-get-messages "+1")))))))
|
||||
|
||||
(ert-deftest noise-receive-receipt-never-downgrades ()
|
||||
"A delivery receipt arriving after a read receipt is ignored."
|
||||
(noise-receive-test--with-db
|
||||
(noise-db-upsert-conversation "+1" :type "direct" :name "Sarah")
|
||||
(noise-db-insert-message "+1" :timestamp 1717930000.0
|
||||
:source "me" :body "hi" :receipt-state "read")
|
||||
(noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+1"
|
||||
"receiptMessage" (noise-receive-test--ht
|
||||
"isDelivery" t "isRead" :false
|
||||
"timestamps" (vector 1717930000000))))
|
||||
(should (string= "read"
|
||||
(gethash "receipt_state"
|
||||
(car (noise-db-get-messages "+1")))))))
|
||||
|
||||
(ert-deftest noise-receive-receipt-ignores-incoming-messages ()
|
||||
"Receipts only apply to own messages, not the contact's."
|
||||
(noise-receive-test--with-db
|
||||
(noise-db-upsert-conversation "+1" :type "direct" :name "Sarah")
|
||||
(noise-db-insert-message "+1" :timestamp 1717930000.0
|
||||
:source "Sarah" :body "hi")
|
||||
(noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+1"
|
||||
"receiptMessage" (noise-receive-test--ht
|
||||
"isDelivery" t "isRead" :false
|
||||
"timestamps" (vector 1717930000000))))
|
||||
(should (string= ""
|
||||
(gethash "receipt_state"
|
||||
(car (noise-db-get-messages "+1")))))))
|
||||
|
||||
(ert-deftest noise-receive-typing-without-buffer ()
|
||||
"Typing indicators for conversations without open buffers are harmless."
|
||||
(noise-receive-test--with-db
|
||||
(should (= 0 (noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+1" "sourceName" "Sarah"
|
||||
"typingMessage" (noise-receive-test--ht
|
||||
"action" "STARTED"
|
||||
"timestamp" 1717930000000)))))))
|
||||
|
||||
(ert-deftest noise-receive-refreshes-open-conversation ()
|
||||
"Receiving a message re-renders an open conversation buffer."
|
||||
(noise-receive-test--with-db
|
||||
(noise-db-upsert-conversation "+1" :type "direct" :name "Sarah"
|
||||
:members '("+1"))
|
||||
(require 'noise-conversation)
|
||||
(with-current-buffer (noise-conversation-open "+1")
|
||||
(noise-receive-test--run
|
||||
(noise-receive-test--ht
|
||||
"sourceNumber" "+1" "sourceName" "Sarah"
|
||||
"dataMessage" (noise-receive-test--ht
|
||||
"timestamp" 1717930000000
|
||||
"message" "new message here")))
|
||||
(should (string-match-p "Sarah> new message here" (buffer-string))))))
|
||||
Reference in New Issue
Block a user