Adds account switching
This commit is contained in:
@@ -17,6 +17,7 @@ Noise is a Signal Messenger client for Emacs, written in Elisp. It communicates
|
|||||||
- The JSON-RPC interface is called over HTTP (not piped subprocess stdio), so requests are `POST http://localhost:9128/api/v1/rpc` with JSON-RPC 2.0 payloads.
|
- The JSON-RPC interface is called over HTTP (not piped subprocess stdio), so requests are `POST http://localhost:9128/api/v1/rpc` with JSON-RPC 2.0 payloads.
|
||||||
- SQLite is the single source of truth for local state: contacts, conversations, messages, read/unread markers.
|
- SQLite is the single source of truth for local state: contacts, conversations, messages, read/unread markers.
|
||||||
- The address is configurable via the `noise-signal-cli-address` defcustom.
|
- The address is configurable via the `noise-signal-cli-address` defcustom.
|
||||||
|
- The Signal account is configurable via the `noise-account` defcustom (nil for a single-account daemon; required when signal-cli has multiple accounts registered). Account-scoped RPC calls go through `noise-rpc-call-for-account`.
|
||||||
|
|
||||||
## User Experience Principles
|
## User Experience Principles
|
||||||
|
|
||||||
|
|||||||
+12
@@ -63,6 +63,9 @@ Below are recipes for several popular Emacs package managers.
|
|||||||
;; Optional: set a custom signal-cli address
|
;; Optional: set a custom signal-cli address
|
||||||
;; (setq noise-signal-cli-address "http://localhost:8080")
|
;; (setq noise-signal-cli-address "http://localhost:8080")
|
||||||
|
|
||||||
|
;; Required when signal-cli has multiple accounts registered:
|
||||||
|
;; (setq noise-account "+15551234567")
|
||||||
|
|
||||||
;; Start Noise automatically
|
;; Start Noise automatically
|
||||||
(noise-mode 1))
|
(noise-mode 1))
|
||||||
#+end_src
|
#+end_src
|
||||||
@@ -130,6 +133,14 @@ Once signal-cli is running and Noise is installed:
|
|||||||
|
|
||||||
- =M-x noise-mode= — toggle the Noise client (opens the chat list)
|
- =M-x noise-mode= — toggle the Noise client (opens the chat list)
|
||||||
- =M-x noise-check-connection= — test connectivity to signal-cli
|
- =M-x noise-check-connection= — test connectivity to signal-cli
|
||||||
|
- =M-x noise-select-account= — pick which registered signal-cli account to use (sets =noise-account=)
|
||||||
|
- =M-x noise-new-chat= — start a new conversation by searching contacts
|
||||||
|
- =M-x noise-new-chat-sync-contacts= — re-sync contacts from signal-cli
|
||||||
|
|
||||||
|
If signal-cli has more than one account registered, set =noise-account=
|
||||||
|
to the phone number you want to use (E.164 format), or run
|
||||||
|
=M-x noise-select-account= to choose interactively. With a single
|
||||||
|
account it can stay nil.
|
||||||
|
|
||||||
*** Keybindings
|
*** Keybindings
|
||||||
|
|
||||||
@@ -140,6 +151,7 @@ Once signal-cli is running and Noise is installed:
|
|||||||
| =n= | Next chat |
|
| =n= | Next chat |
|
||||||
| =p= | Previous chat |
|
| =p= | Previous chat |
|
||||||
| =RET= | Open selected chat |
|
| =RET= | Open selected chat |
|
||||||
|
| =c= | New chat (search contacts) |
|
||||||
| =m= | Mark chat as read |
|
| =m= | Mark chat as read |
|
||||||
| =g= | Refresh chat list |
|
| =g= | Refresh chat list |
|
||||||
| =/= | Filter chats |
|
| =/= | Filter chats |
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'noise-db)
|
(require 'noise-db)
|
||||||
|
(require 'noise-new-chat)
|
||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
|
|
||||||
(defvar noise-chat-list-mode-map
|
(defvar noise-chat-list-mode-map
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
(define-key map (kbd "p") #'previous-line)
|
(define-key map (kbd "p") #'previous-line)
|
||||||
(define-key map (kbd "RET") #'noise-chat-list-open)
|
(define-key map (kbd "RET") #'noise-chat-list-open)
|
||||||
(define-key map (kbd "m") #'noise-chat-list-mark-read)
|
(define-key map (kbd "m") #'noise-chat-list-mark-read)
|
||||||
|
(define-key map (kbd "c") #'noise-new-chat)
|
||||||
(define-key map (kbd "g") #'noise-chat-list-refresh)
|
(define-key map (kbd "g") #'noise-chat-list-refresh)
|
||||||
(define-key map (kbd "/") #'noise-chat-list-filter)
|
(define-key map (kbd "/") #'noise-chat-list-filter)
|
||||||
(define-key map (kbd "q") #'quit-window)
|
(define-key map (kbd "q") #'quit-window)
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
;;; noise-new-chat.el --- Start new conversations from contacts -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
|
||||||
|
;; Provides `noise-new-chat' — start a new conversation by searching
|
||||||
|
;; contacts with minibuffer completion. Contacts are synced from
|
||||||
|
;; signal-cli via the listContacts JSON-RPC method and cached in the
|
||||||
|
;; local SQLite database.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'noise-rpc)
|
||||||
|
(require 'noise-db)
|
||||||
|
(require 'cl-lib)
|
||||||
|
|
||||||
|
(declare-function noise-chat-list--render "noise-chat-list")
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun noise-new-chat ()
|
||||||
|
"Start a new conversation by searching contacts.
|
||||||
|
Syncs contacts from signal-cli when the local database has none.
|
||||||
|
Candidates match on both name and phone number."
|
||||||
|
(interactive)
|
||||||
|
(noise-db-init)
|
||||||
|
(unless (noise-db-get-all-contacts)
|
||||||
|
(noise-new-chat-sync-contacts))
|
||||||
|
(let* ((contacts (noise-db-get-all-contacts))
|
||||||
|
(collection (mapcar (lambda (c)
|
||||||
|
(cons (noise-new-chat--candidate c) c))
|
||||||
|
contacts)))
|
||||||
|
(unless collection
|
||||||
|
(user-error "No contacts found. Is signal-cli synced?"))
|
||||||
|
(let* ((choice (completing-read "New chat with: " collection nil t))
|
||||||
|
(contact (cdr (assoc choice collection))))
|
||||||
|
(when contact
|
||||||
|
(noise-new-chat-with-contact (gethash "id" contact)
|
||||||
|
(gethash "name" contact))))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun noise-new-chat-sync-contacts ()
|
||||||
|
"Fetch contacts from signal-cli and store them in the local database.
|
||||||
|
Returns the number of contacts synced."
|
||||||
|
(interactive)
|
||||||
|
(noise-db-init)
|
||||||
|
(let ((contacts (noise-rpc-call-for-account "listContacts"))
|
||||||
|
(count 0))
|
||||||
|
(seq-do
|
||||||
|
(lambda (c)
|
||||||
|
(when-let ((id (noise-new-chat--contact-id c)))
|
||||||
|
(noise-db-upsert-contact
|
||||||
|
id
|
||||||
|
:name (noise-new-chat--contact-name c)
|
||||||
|
:color (noise-new-chat--field c "color")
|
||||||
|
:profile-name (noise-new-chat--profile-name c))
|
||||||
|
(cl-incf count)))
|
||||||
|
contacts)
|
||||||
|
(when (called-interactively-p 'interactive)
|
||||||
|
(message "Synced %d contacts from signal-cli" count))
|
||||||
|
count))
|
||||||
|
|
||||||
|
(defun noise-new-chat-with-contact (contact-id name)
|
||||||
|
"Open (creating if needed) a direct conversation with CONTACT-ID.
|
||||||
|
NAME is used as the conversation name for a new conversation.
|
||||||
|
The conversation ID for a direct chat is the contact ID itself."
|
||||||
|
(unless (noise-db-get-conversation contact-id)
|
||||||
|
(noise-db-upsert-conversation contact-id
|
||||||
|
:type "direct"
|
||||||
|
:name (if (and name (not (string-empty-p name)))
|
||||||
|
name
|
||||||
|
contact-id)
|
||||||
|
:members (list contact-id)))
|
||||||
|
(when-let ((buf (get-buffer "*signal*")))
|
||||||
|
(with-current-buffer buf
|
||||||
|
(noise-chat-list--render)))
|
||||||
|
;; Conversation buffers are not implemented yet; land on the chat list.
|
||||||
|
(message "Conversation with %s ready (conversation buffer not yet implemented)"
|
||||||
|
(or name contact-id)))
|
||||||
|
|
||||||
|
(defun noise-new-chat--candidate (contact)
|
||||||
|
"Format CONTACT (a hash table from noise-db) as a completion candidate.
|
||||||
|
Includes the phone number so completion can match on it."
|
||||||
|
(let ((id (gethash "id" contact))
|
||||||
|
(name (gethash "name" contact)))
|
||||||
|
(if (or (null name) (string-empty-p name) (string= name id))
|
||||||
|
id
|
||||||
|
(format "%s (%s)" name id))))
|
||||||
|
|
||||||
|
(defun noise-new-chat--field (obj key)
|
||||||
|
"Get KEY from hash table OBJ, treating JSON null as nil."
|
||||||
|
(when obj
|
||||||
|
(let ((v (gethash key obj)))
|
||||||
|
(unless (eq v :null) v))))
|
||||||
|
|
||||||
|
(defun noise-new-chat--contact-id (contact)
|
||||||
|
"Extract the contact ID from a signal-cli CONTACT object.
|
||||||
|
Prefers the phone number, falling back to the UUID."
|
||||||
|
(or (noise-new-chat--field contact "number")
|
||||||
|
(noise-new-chat--field contact "uuid")))
|
||||||
|
|
||||||
|
(defun noise-new-chat--profile-name (contact)
|
||||||
|
"Extract the profile display name from a signal-cli CONTACT object."
|
||||||
|
(when-let ((profile (noise-new-chat--field contact "profile")))
|
||||||
|
(let ((full (string-trim
|
||||||
|
(concat (or (noise-new-chat--field profile "givenName") "")
|
||||||
|
" "
|
||||||
|
(or (noise-new-chat--field profile "familyName") "")))))
|
||||||
|
(unless (string-empty-p full) full))))
|
||||||
|
|
||||||
|
(defun noise-new-chat--contact-name (contact)
|
||||||
|
"Pick the best display name for a signal-cli CONTACT object.
|
||||||
|
Prefers the local contact name, then the profile name, then the
|
||||||
|
username, then the phone number."
|
||||||
|
(or (let ((name (noise-new-chat--field contact "name")))
|
||||||
|
(and name (not (string-empty-p name)) name))
|
||||||
|
(noise-new-chat--profile-name contact)
|
||||||
|
(noise-new-chat--field contact "username")
|
||||||
|
(noise-new-chat--contact-id contact)))
|
||||||
|
|
||||||
|
(provide 'noise-new-chat)
|
||||||
|
;;; noise-new-chat.el ends here
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
|
|
||||||
(defvar noise-signal-cli-address)
|
(defvar noise-signal-cli-address)
|
||||||
|
(defvar noise-account)
|
||||||
|
|
||||||
(define-error 'noise-rpc-error "signal-cli JSON-RPC error")
|
(define-error 'noise-rpc-error "signal-cli JSON-RPC error")
|
||||||
|
|
||||||
@@ -67,6 +68,16 @@ Signals `noise-rpc-error` on failure or connection error."
|
|||||||
result))
|
result))
|
||||||
(kill-buffer buf)))))
|
(kill-buffer buf)))))
|
||||||
|
|
||||||
|
(defun noise-rpc-call-for-account (method &rest params)
|
||||||
|
"Call signal-cli JSON-RPC METHOD with PARAMS for the configured account.
|
||||||
|
Like `noise-rpc-call', but when `noise-account' is set the `account'
|
||||||
|
parameter is added to the request. Account-scoped methods (such as
|
||||||
|
listContacts or send) require it when the daemon serves multiple
|
||||||
|
accounts."
|
||||||
|
(if (and (boundp 'noise-account) noise-account)
|
||||||
|
(apply #'noise-rpc-call method :account noise-account params)
|
||||||
|
(apply #'noise-rpc-call method params)))
|
||||||
|
|
||||||
(defun noise-rpc--parse-response (raw)
|
(defun noise-rpc--parse-response (raw)
|
||||||
"Parse RAW JSON-RPC response body.
|
"Parse RAW JSON-RPC response body.
|
||||||
Returns the `result` on success.
|
Returns the `result` on success.
|
||||||
|
|||||||
+5
-2
@@ -9,6 +9,7 @@
|
|||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'noise-db)
|
(require 'noise-db)
|
||||||
|
(require 'noise-new-chat)
|
||||||
|
|
||||||
(defun noise-switcher ()
|
(defun noise-switcher ()
|
||||||
"Switch to a Signal conversation via minibuffer completion.
|
"Switch to a Signal conversation via minibuffer completion.
|
||||||
@@ -31,8 +32,9 @@ Shows existing conversations and contacts without conversations."
|
|||||||
(if (gethash "id" item)
|
(if (gethash "id" item)
|
||||||
;; It's a conversation (has an id)
|
;; It's a conversation (has an id)
|
||||||
(message "Open conversation: %s (not yet implemented)" (gethash "name" item))
|
(message "Open conversation: %s (not yet implemented)" (gethash "name" item))
|
||||||
;; It's just a contact — create conversation stub
|
;; It's a contact without a conversation — start one
|
||||||
(message "New conversation with: %s (not yet implemented)" choice))
|
(noise-new-chat-with-contact (gethash "contact_id" item)
|
||||||
|
(gethash "name" item)))
|
||||||
(message "Unknown selection: %s" choice))))))
|
(message "Unknown selection: %s" choice))))))
|
||||||
|
|
||||||
(defun noise-switcher--collection ()
|
(defun noise-switcher--collection ()
|
||||||
@@ -57,6 +59,7 @@ Returns an alist of (display-string . hash-table)."
|
|||||||
(unless (gethash contact-id conv-contacts)
|
(unless (gethash contact-id conv-contacts)
|
||||||
;; Contact without conversation - add a stub
|
;; Contact without conversation - add a stub
|
||||||
(let ((ht (make-hash-table :test 'equal)))
|
(let ((ht (make-hash-table :test 'equal)))
|
||||||
|
(puthash "contact_id" contact-id ht)
|
||||||
(puthash "name" name ht)
|
(puthash "name" name ht)
|
||||||
(puthash "type" "direct" ht)
|
(puthash "type" "direct" ht)
|
||||||
(puthash "unread_count" 0 ht)
|
(puthash "unread_count" 0 ht)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
(require 'noise-rpc)
|
(require 'noise-rpc)
|
||||||
(require 'noise-chat-list)
|
(require 'noise-chat-list)
|
||||||
|
(require 'noise-new-chat)
|
||||||
|
|
||||||
(defgroup noise nil
|
(defgroup noise nil
|
||||||
"Signal Messenger client for Emacs."
|
"Signal Messenger client for Emacs."
|
||||||
@@ -26,6 +27,37 @@ The default port is 8080, matching signal-cli's `--http` default."
|
|||||||
:type 'string
|
:type 'string
|
||||||
:group 'noise)
|
:group 'noise)
|
||||||
|
|
||||||
|
(defcustom noise-account nil
|
||||||
|
"The Signal account to use, as a phone number in E.164 format.
|
||||||
|
signal-cli can have multiple accounts registered. When the daemon
|
||||||
|
serves more than one account, every request must say which account it
|
||||||
|
is for, so this must be set (e.g. \"+15551234567\"). When nil, the
|
||||||
|
daemon is assumed to serve a single account (started with `-a`).
|
||||||
|
|
||||||
|
Use `noise-select-account' to pick one interactively from the
|
||||||
|
accounts registered with signal-cli."
|
||||||
|
:type '(choice (const :tag "Single-account daemon" nil)
|
||||||
|
(string :tag "Phone number (E.164)"))
|
||||||
|
:group 'noise)
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun noise-select-account ()
|
||||||
|
"Select which signal-cli account Noise should use.
|
||||||
|
Queries the daemon for registered accounts and sets `noise-account'."
|
||||||
|
(interactive)
|
||||||
|
(let* ((accounts (noise-rpc-call "listAccounts"))
|
||||||
|
(numbers (mapcar (lambda (a) (gethash "number" a))
|
||||||
|
(append accounts nil))))
|
||||||
|
(cond
|
||||||
|
((null numbers)
|
||||||
|
(user-error "No accounts registered with signal-cli"))
|
||||||
|
((null (cdr numbers))
|
||||||
|
(setq noise-account (car numbers)))
|
||||||
|
(t
|
||||||
|
(setq noise-account
|
||||||
|
(completing-read "Signal account: " numbers nil t))))
|
||||||
|
(message "Using Signal account %s" noise-account)))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun noise-check-connection ()
|
(defun noise-check-connection ()
|
||||||
"Check connectivity to the signal-cli JSON-RPC daemon."
|
"Check connectivity to the signal-cli JSON-RPC daemon."
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
;;; noise-new-chat-test.el --- Tests for noise-new-chat -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
(require 'ert)
|
||||||
|
(require 'noise-new-chat)
|
||||||
|
|
||||||
|
(defvar noise-new-chat-test--db
|
||||||
|
(expand-file-name "noise-new-chat-test.db" temporary-file-directory))
|
||||||
|
|
||||||
|
(defun noise-new-chat-test--setup ()
|
||||||
|
"Set up a fresh test database."
|
||||||
|
(require 'noise-db)
|
||||||
|
(let ((noise-db-file noise-new-chat-test--db))
|
||||||
|
(when (file-exists-p noise-db-file)
|
||||||
|
(delete-file noise-db-file))
|
||||||
|
(noise-db-init)))
|
||||||
|
|
||||||
|
(defun noise-new-chat-test--teardown ()
|
||||||
|
"Clean up the test database."
|
||||||
|
(require 'noise-db)
|
||||||
|
(noise-db--close)
|
||||||
|
(when (file-exists-p noise-new-chat-test--db)
|
||||||
|
(delete-file noise-new-chat-test--db)))
|
||||||
|
|
||||||
|
(defmacro noise-new-chat-test--with-db (&rest body)
|
||||||
|
"Run BODY with a fresh test database bound to `noise-db-file'."
|
||||||
|
`(let ((noise-db-file noise-new-chat-test--db))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(noise-new-chat-test--setup)
|
||||||
|
,@body)
|
||||||
|
(noise-new-chat-test--teardown))))
|
||||||
|
|
||||||
|
(defun noise-new-chat-test--contact (&rest kvs)
|
||||||
|
"Build a hash table simulating a signal-cli contact from KVS pairs."
|
||||||
|
(let ((ht (make-hash-table :test 'equal)))
|
||||||
|
(while kvs
|
||||||
|
(puthash (car kvs) (cadr kvs) ht)
|
||||||
|
(setq kvs (cddr kvs)))
|
||||||
|
ht))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat--contact-id-prefers-number ()
|
||||||
|
"Contact ID should prefer the phone number over the UUID."
|
||||||
|
(let ((c (noise-new-chat-test--contact "number" "+15551234567"
|
||||||
|
"uuid" "abc-123")))
|
||||||
|
(should (string= "+15551234567" (noise-new-chat--contact-id c)))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat--contact-id-falls-back-to-uuid ()
|
||||||
|
"Contact ID should fall back to UUID when number is null."
|
||||||
|
(let ((c (noise-new-chat-test--contact "number" :null
|
||||||
|
"uuid" "abc-123")))
|
||||||
|
(should (string= "abc-123" (noise-new-chat--contact-id c)))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat--contact-name-prefers-local-name ()
|
||||||
|
"Display name should prefer the local contact name."
|
||||||
|
(let ((c (noise-new-chat-test--contact
|
||||||
|
"number" "+15551234567"
|
||||||
|
"name" "Alice"
|
||||||
|
"profile" (noise-new-chat-test--contact "givenName" "Alicia"))))
|
||||||
|
(should (string= "Alice" (noise-new-chat--contact-name c)))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat--contact-name-uses-profile ()
|
||||||
|
"Display name should fall back to the profile name."
|
||||||
|
(let ((c (noise-new-chat-test--contact
|
||||||
|
"number" "+15551234567"
|
||||||
|
"name" ""
|
||||||
|
"profile" (noise-new-chat-test--contact "givenName" "Alicia"
|
||||||
|
"familyName" "Jones"))))
|
||||||
|
(should (string= "Alicia Jones" (noise-new-chat--contact-name c)))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat--contact-name-falls-back-to-number ()
|
||||||
|
"Display name should fall back to the phone number when nothing else."
|
||||||
|
(let ((c (noise-new-chat-test--contact "number" "+15551234567"
|
||||||
|
"name" ""
|
||||||
|
"profile" :null)))
|
||||||
|
(should (string= "+15551234567" (noise-new-chat--contact-name c)))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat--candidate-includes-number ()
|
||||||
|
"Completion candidates should include name and number."
|
||||||
|
(let ((ht (make-hash-table :test 'equal)))
|
||||||
|
(puthash "id" "+15551234567" ht)
|
||||||
|
(puthash "name" "Alice" ht)
|
||||||
|
(should (string= "Alice (+15551234567)" (noise-new-chat--candidate ht)))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat--candidate-number-only ()
|
||||||
|
"Candidates with no name should be just the number."
|
||||||
|
(let ((ht (make-hash-table :test 'equal)))
|
||||||
|
(puthash "id" "+15551234567" ht)
|
||||||
|
(puthash "name" "" ht)
|
||||||
|
(should (string= "+15551234567" (noise-new-chat--candidate ht)))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat-sync-contacts-stores-contacts ()
|
||||||
|
"Syncing should upsert contacts returned by signal-cli into the DB."
|
||||||
|
(noise-new-chat-test--with-db
|
||||||
|
(cl-letf (((symbol-function 'noise-rpc-call-for-account)
|
||||||
|
(lambda (method &rest _)
|
||||||
|
(should (string= "listContacts" method))
|
||||||
|
(vector
|
||||||
|
(noise-new-chat-test--contact "number" "+1" "name" "Sarah")
|
||||||
|
(noise-new-chat-test--contact "number" "+2" "name" ""
|
||||||
|
"profile"
|
||||||
|
(noise-new-chat-test--contact
|
||||||
|
"givenName" "Samir"
|
||||||
|
"familyName" "Darwish"))
|
||||||
|
;; No number and no uuid — should be skipped
|
||||||
|
(noise-new-chat-test--contact "name" "Ghost")))))
|
||||||
|
(should (= 2 (noise-new-chat-sync-contacts)))
|
||||||
|
(should (string= "Sarah" (gethash "name" (noise-db-get-contact "+1"))))
|
||||||
|
(should (string= "Samir Darwish"
|
||||||
|
(gethash "name" (noise-db-get-contact "+2")))))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat-sync-contacts-uses-account ()
|
||||||
|
"Syncing should go through the account-aware RPC helper."
|
||||||
|
(noise-new-chat-test--with-db
|
||||||
|
(defvar noise-account)
|
||||||
|
(let ((noise-account "+19998887777")
|
||||||
|
(seen-account nil))
|
||||||
|
(cl-letf (((symbol-function 'noise-rpc-call)
|
||||||
|
(lambda (_method &rest params)
|
||||||
|
(setq seen-account (plist-get params :account))
|
||||||
|
(vector))))
|
||||||
|
(noise-new-chat-sync-contacts)
|
||||||
|
(should (string= "+19998887777" seen-account))))))
|
||||||
|
|
||||||
|
(ert-deftest noise-new-chat-with-contact-creates-conversation ()
|
||||||
|
"Starting a chat with a contact should create a direct conversation."
|
||||||
|
(noise-new-chat-test--with-db
|
||||||
|
(noise-db-upsert-contact "+1" :name "Sarah")
|
||||||
|
(noise-new-chat-with-contact "+1" "Sarah")
|
||||||
|
(let ((conv (noise-db-get-conversation "+1")))
|
||||||
|
(should conv)
|
||||||
|
(should (string= "direct" (gethash "type" conv)))
|
||||||
|
(should (string= "Sarah" (gethash "name" conv)))
|
||||||
|
(should (equal '("+1")
|
||||||
|
(mapcar (lambda (m) (gethash "contact_id" m))
|
||||||
|
(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."
|
||||||
|
(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))))))
|
||||||
Reference in New Issue
Block a user