aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-14 01:12:21 -0500
committerCraig Jennings <c@cjennings.net>2026-07-14 01:12:21 -0500
commita9736bcf1f1d683656d7222bdda26c1a7ef73986 (patch)
treede366569e3922a036bddc040eda1d47b49940255 /tests
parent42664cd1097a7bcea5de629bbddb3a58eadcdb57 (diff)
downloaddotemacs-a9736bcf1f1d683656d7222bdda26c1a7ef73986.tar.gz
dotemacs-a9736bcf1f1d683656d7222bdda26c1a7ef73986.zip
refactor(signal): retire the in-Emacs signel client to the archive
Agents drive Signal via signal-cli and signal-mcp, so the interactive client earns no keep. I moved signal-config.el and its seven test files to archive/ per the pasture convention, dropped the require from init.el, removed the dashboard's Signal launcher (row sizes now 5 4 3 2, tests updated), and unregistered the C-; M prefix from the running daemon. The ~/code/signel fork repo is untouched. The spec record stays IMPLEMENTED with a retirement history line, and the messenger-unification draft carries a premise-shift note.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-dashboard-config-launchers.el23
-rw-r--r--tests/test-signal-config--contact-cache.el61
-rw-r--r--tests/test-signal-config-notify.el150
-rw-r--r--tests/test-signal-config.el410
-rw-r--r--tests/test-signel-cancel-input.el74
-rw-r--r--tests/test-signel-input-preservation.el68
-rw-r--r--tests/test-signel-notify-function.el89
-rw-r--r--tests/test-signel-rpc-dispatch.el94
8 files changed, 11 insertions, 958 deletions
diff --git a/tests/test-dashboard-config-launchers.el b/tests/test-dashboard-config-launchers.el
index 53c46caa..76fbcc42 100644
--- a/tests/test-dashboard-config-launchers.el
+++ b/tests/test-dashboard-config-launchers.el
@@ -28,20 +28,21 @@
;; Telegram moved from "g" to "G" so "g" is free for dashboard refresh.
;; Signal ("S") added as the 14th launcher.
;; Weather ("w") added after Agenda as the 15th launcher (top-row daily glance).
-(defconst test-dash--keys '("c" "d" "t" "a" "w" "r" "b" "f" "m" "e" "i" "G" "s" "l" "S"))
+(defconst test-dash--keys '("c" "d" "t" "a" "w" "r" "b" "f" "m" "e" "i" "G" "s" "l"))
;; ----------------------------- launcher table --------------------------------
(ert-deftest test-dashboard-launchers-keys-in-order ()
- "Normal: 15 launchers with the expected keys in display order."
- (should (= 15 (length cj/dashboard--launchers)))
+ "Normal: 14 launchers with the expected keys in display order.
+(Signal left the table when the signel client was retired to archive/.)"
+ (should (= 14 (length cj/dashboard--launchers)))
(should (equal test-dash--keys (mapcar (lambda (l) (nth 0 l)) cj/dashboard--launchers))))
(ert-deftest test-dashboard-launchers-labels-in-order ()
"Normal: labels in display order (Telegram and Slack reordered so Slack sits
next to Linear on the last navigator row)."
(should (equal '("Code" "Files" "Terminal" "Agenda" "Weather" "Feeds" "Books"
- "Flashcards" "Music" "Email" "IRC" "Telegram" "Slack" "Linear" "Signal")
+ "Flashcards" "Music" "Email" "IRC" "Telegram" "Slack" "Linear")
(mapcar (lambda (l) (nth 3 l)) cj/dashboard--launchers))))
(ert-deftest test-dashboard-row-sizes-cover-all-launchers ()
@@ -51,9 +52,9 @@ next to Linear on the last navigator row)."
;; --------------------------- navigator rows ----------------------------------
-(ert-deftest test-dashboard-navigator-rows-grouped-5-4-3-3 ()
- "Normal: navigator derives rows per `cj/dashboard--row-sizes' (5 4 3 3), with
-Weather joining the top row and Slack, Linear, and Signal sharing the last row."
+(ert-deftest test-dashboard-navigator-rows-grouped-5-4-3-2 ()
+ "Normal: navigator derives rows per `cj/dashboard--row-sizes' (5 4 3 2), with
+Weather joining the top row and Slack and Linear pairing on the last row."
(cl-letf (((symbol-function 'nerd-icons-faicon) (lambda (n &rest _) (concat "I:" n)))
((symbol-function 'nerd-icons-devicon) (lambda (n &rest _) (concat "I:" n)))
((symbol-function 'nerd-icons-mdicon) (lambda (n &rest _) (concat "I:" n)))
@@ -62,10 +63,10 @@ Weather joining the top row and Slack, Linear, and Signal sharing the last row."
((symbol-function 'nerd-icons-wicon) (lambda (n &rest _) (concat "I:" n))))
(let ((rows (cj/dashboard--navigator-rows)))
(should (= 4 (length rows)))
- (should (equal '(5 4 3 3) (mapcar #'length rows)))
+ (should (equal '(5 4 3 2) (mapcar #'length rows)))
(should (equal '("Code" "Files" "Terminal" "Agenda" "Weather")
(mapcar (lambda (b) (nth 1 b)) (nth 0 rows))))
- (should (equal '("Slack" "Linear" "Signal")
+ (should (equal '("Slack" "Linear")
(mapcar (lambda (b) (nth 1 b)) (nth 3 rows))))
(let ((btn (car (car rows)))) ; (icon label tooltip action nil " " "")
(should (string= "I:nf-fa-code" (nth 0 btn)))
@@ -100,7 +101,6 @@ Weather joining the top row and Slack, Linear, and Signal sharing the last row."
((symbol-function 'cj/slack-start) (lambda (&rest _) (push 'slack calls)))
((symbol-function 'cj/telega) (lambda (&rest _) (push 'tg calls)))
((symbol-function 'pearl-list-issues) (lambda (&rest _) (push 'linear calls)))
- ((symbol-function 'cj/signel-message) (lambda (&rest _) (push 'signal calls)))
;; wttrin is invoked via `call-interactively', so the stub must be
;; a command -- a plain variadic lambda masked the real arity bug.
((symbol-function 'wttrin) (lambda (&rest _) (interactive) (push 'weather calls))))
@@ -112,9 +112,8 @@ Weather joining the top row and Slack, Linear, and Signal sharing the last row."
(should (memq 'linear calls))
(should (memq 'm-toggle calls))
(should (memq 'm-load calls))
- (should (memq 'signal calls))
(should (memq 'weather calls))
- (should (= 16 (length calls)))))) ; 15 keys, Music fires two
+ (should (= 15 (length calls)))))) ; 14 keys, Music fires two
(provide 'test-dashboard-config-launchers)
;;; test-dashboard-config-launchers.el ends here
diff --git a/tests/test-signal-config--contact-cache.el b/tests/test-signal-config--contact-cache.el
deleted file mode 100644
index 720e0c3a..00000000
--- a/tests/test-signal-config--contact-cache.el
+++ /dev/null
@@ -1,61 +0,0 @@
-;;; test-signal-config--contact-cache.el --- Contact-cache lifecycle tests -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; The picker's contact cache has two lifecycle bugs from the 2026-06 config
-;; audit: (1) its docstring promised clearing on signel-stop but nothing
-;; cleared it, so a stale list survived a relink/reconnect; (2) a
-;; fetched-and-empty list was cached as nil, indistinguishable from a cold
-;; cache, so a zero-contact account re-ran the blocking fetch (up to
-;; `cj/signel-fetch-timeout') on every picker open. The fix names the empty
-;; state with an `empty' sentinel and clears the cache via a named function
-;; advised onto `signel-stop'.
-;;
-;; Boundary mocks only (the RPC send, the prompt); the cache logic runs
-;; real. The ensure-started branches the audit called untested were
-;; already covered in test-signal-config.el -- that claim was stale.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-(require 'signal-config)
-
-;;; ------------------------- clear-on-stop (F1) ------------------------------
-
-(ert-deftest test-signal-config-clear-contact-cache-resets ()
- "Normal: the clear function empties the cache back to cold (nil)."
- (let ((cj/signel--contact-cache '(("Al (+1)" . "+1"))))
- (cj/signel--clear-contact-cache)
- (should (null cj/signel--contact-cache))))
-
-(ert-deftest test-signal-config-clear-contact-cache-advises-stop ()
- "Normal: `signel-stop' carries the clear advice, so the docstring's
-\"cleared on stop/restart\" promise is real."
- (should (advice-member-p #'cj/signel--clear-contact-cache 'signel-stop)))
-
-;;; ------------------------- empty sentinel (F2) ------------------------------
-
-(ert-deftest test-signal-config-fetch-empty-caches-sentinel ()
- "Boundary: a fetched-and-empty list caches the `empty' sentinel, not nil.
-nil means cold cache; without the sentinel a zero-contact account re-ran
-the blocking fetch on every picker open."
- (let ((cj/signel--contact-cache nil)
- (captured-callback nil))
- (cl-letf (((symbol-function 'signel--send-rpc)
- (lambda (_method _params _buf callback)
- (setq captured-callback callback))))
- (cj/signel--fetch-contacts)
- (funcall captured-callback '()))
- (should (eq cj/signel--contact-cache 'empty))))
-
-(ert-deftest test-signal-config-cached-contacts-unwraps-sentinel ()
- "Normal: the cache reader returns the alist, and nil for the sentinel."
- (let ((cj/signel--contact-cache '(("Al (+1)" . "+1"))))
- (should (equal (cj/signel--cached-contacts) '(("Al (+1)" . "+1")))))
- (let ((cj/signel--contact-cache 'empty))
- (should (null (cj/signel--cached-contacts)))))
-
-(provide 'test-signal-config--contact-cache)
-;;; test-signal-config--contact-cache.el ends here
diff --git a/tests/test-signal-config-notify.el b/tests/test-signal-config-notify.el
deleted file mode 100644
index 1a772289..00000000
--- a/tests/test-signal-config-notify.el
+++ /dev/null
@@ -1,150 +0,0 @@
-;;; test-signal-config-notify.el --- Tests for the signal-config notification slice -*- lexical-binding: t -*-
-
-;;; Commentary:
-;; ERT tests for the notification slice of `signal-config': the pure
-;; body formatter (whitespace collapse + truncation to
-;; `cj/signal--notify-body-max') and `cj/signel--notify' routing (the
-;; suppression gate, the notify-script path with the sound flag, and
-;; the `notifications-notify' fallback). Spec: the "Notification
-;; slice" addendum in docs/specs/signal-client-spec-doing.org. No signal-cli or
-;; linked account needed.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-;; signel is the fork at ~/code/signel; signal-config wires it via
-;; use-package but these tests need the symbols available directly.
-(eval-and-compile
- (add-to-list 'load-path (expand-file-name "~/code/signel")))
-(require 'signel)
-
-(require 'signal-config)
-
-;;; cj/signal--format-notify-body
-
-(ert-deftest test-signal-config-format-notify-body-passthrough ()
- "Normal: short single-line text passes through unchanged."
- (should (equal (cj/signal--format-notify-body "lunch at noon?")
- "lunch at noon?")))
-
-(ert-deftest test-signal-config-format-notify-body-collapses-whitespace ()
- "Normal: newlines and whitespace runs collapse to single spaces."
- (should (equal (cj/signal--format-notify-body "two\nlines\n\nhere")
- "two lines here"))
- (should (equal (cj/signal--format-notify-body "tabs\t\tand spaces")
- "tabs and spaces")))
-
-(ert-deftest test-signal-config-format-notify-body-trims ()
- "Boundary: leading and trailing whitespace is trimmed."
- (should (equal (cj/signal--format-notify-body " hi ") "hi")))
-
-(ert-deftest test-signal-config-format-notify-body-empty ()
- "Boundary: the empty string stays empty."
- (should (equal (cj/signal--format-notify-body "") "")))
-
-(ert-deftest test-signal-config-format-notify-body-exact-limit ()
- "Boundary: a body exactly at the limit is untouched."
- (let ((s (make-string cj/signal--notify-body-max ?x)))
- (should (equal (cj/signal--format-notify-body s) s))))
-
-(ert-deftest test-signal-config-format-notify-body-truncates-over-limit ()
- "Boundary: over-limit text truncates to the limit, ending in an ellipsis."
- (let* ((s (make-string (1+ cj/signal--notify-body-max) ?x))
- (out (cj/signal--format-notify-body s)))
- (should (= (length out) cj/signal--notify-body-max))
- (should (string-suffix-p "…" out))))
-
-(ert-deftest test-signal-config-format-notify-body-unicode ()
- "Boundary: multibyte text truncates by characters, not bytes."
- (let* ((s (make-string (+ cj/signal--notify-body-max 10) ?é))
- (out (cj/signal--format-notify-body s)))
- (should (= (length out) cj/signal--notify-body-max))
- (should (string-suffix-p "…" out))))
-
-;;; cj/signel--notify routing
-
-(ert-deftest test-signal-config-notify-suppressed-when-viewing ()
- "Normal: nothing fires when the suppression predicate says no."
- (let (script-calls fallback-calls)
- (cl-letf (((symbol-function 'cj/signal--should-notify-p)
- (lambda (_chat-id) nil))
- ((symbol-function 'start-process)
- (lambda (&rest args) (push args script-calls) nil))
- ((symbol-function 'notifications-notify)
- (lambda (&rest args) (push args fallback-calls) nil)))
- (cj/signel--notify "+15551234567" "Alice" "hi"))
- (should-not script-calls)
- (should-not fallback-calls)))
-
-(ert-deftest test-signal-config-notify-script-silent-by-default ()
- "Normal: with the script present and sound off, runs notify info --silent."
- (let (script-calls)
- (cl-letf (((symbol-function 'cj/signal--should-notify-p)
- (lambda (_chat-id) t))
- ((symbol-function 'executable-find)
- (lambda (p &optional _remote)
- (when (equal p "notify") "/usr/bin/notify")))
- ((symbol-function 'start-process)
- (lambda (&rest args) (push args script-calls) nil))
- ((symbol-function 'notifications-notify)
- (lambda (&rest _)
- (error "Fallback must not fire when the script is present"))))
- (let ((cj/signel-notify-sound nil))
- (cj/signel--notify "+15551234567" "Alice" "hi")))
- (should (= (length script-calls) 1))
- ;; start-process args: (NAME BUFFER PROGRAM &rest PROGRAM-ARGS);
- ;; PROGRAM is the path executable-find resolved, not the bare name.
- (should (equal (nthcdr 2 (car script-calls))
- '("/usr/bin/notify" "info" "Signal: Alice" "hi" "--silent")))))
-
-(ert-deftest test-signal-config-notify-sound-enabled-drops-silent ()
- "Normal: with `cj/signel-notify-sound' non-nil, --silent is omitted."
- (let (script-calls)
- (cl-letf (((symbol-function 'cj/signal--should-notify-p)
- (lambda (_chat-id) t))
- ((symbol-function 'executable-find)
- (lambda (p &optional _remote)
- (when (equal p "notify") "/usr/bin/notify")))
- ((symbol-function 'start-process)
- (lambda (&rest args) (push args script-calls) nil)))
- (let ((cj/signel-notify-sound t))
- (cj/signel--notify "+15551234567" "Alice" "hi")))
- (should (equal (nthcdr 2 (car script-calls))
- '("/usr/bin/notify" "info" "Signal: Alice" "hi")))))
-
-(ert-deftest test-signal-config-notify-fallback-when-script-missing ()
- "Error: without the script on PATH, falls back to notifications-notify."
- (let (script-calls fallback-calls)
- (cl-letf (((symbol-function 'cj/signal--should-notify-p)
- (lambda (_chat-id) t))
- ((symbol-function 'executable-find)
- (lambda (_p &optional _remote) nil))
- ((symbol-function 'start-process)
- (lambda (&rest args) (push args script-calls) nil))
- ((symbol-function 'notifications-notify)
- (lambda (&rest args) (push args fallback-calls) nil)))
- (cj/signel--notify "+15551234567" "Alice" "hi"))
- (should-not script-calls)
- (should (= (length fallback-calls) 1))
- (let ((args (car fallback-calls)))
- (should (equal (plist-get args :title) "Signal: Alice"))
- (should (equal (plist-get args :body) "hi")))))
-
-(ert-deftest test-signal-config-notify-formats-body-before-send ()
- "Normal: the body runs through the formatter before reaching the script."
- (let (script-calls)
- (cl-letf (((symbol-function 'cj/signal--should-notify-p)
- (lambda (_chat-id) t))
- ((symbol-function 'executable-find)
- (lambda (p &optional _remote)
- (when (equal p "notify") "/usr/bin/notify")))
- ((symbol-function 'start-process)
- (lambda (&rest args) (push args script-calls) nil)))
- (let ((cj/signel-notify-sound nil))
- (cj/signel--notify "+15551234567" "Alice" "first line\nsecond line")))
- (should (equal (nth 5 (car script-calls)) "first line second line"))))
-
-(provide 'test-signal-config-notify)
-;;; test-signal-config-notify.el ends here
diff --git a/tests/test-signal-config.el b/tests/test-signal-config.el
deleted file mode 100644
index f8bf4410..00000000
--- a/tests/test-signal-config.el
+++ /dev/null
@@ -1,410 +0,0 @@
-;;; test-signal-config.el --- Tests for signal-config -*- lexical-binding: t -*-
-
-;;; Commentary:
-;; ERT tests for the pure helper layer of `signal-config': contact-list
-;; parsing for the contact picker, and the notify-when-not-viewing
-;; predicate. These need neither signal-cli nor a linked account.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-(require 'json)
-
-;; signel is the fork at ~/code/signel; signal-config wires it via
-;; use-package but the connection-guard/fetch tests need the symbols
-;; available directly.
-(eval-and-compile
- (add-to-list 'load-path (expand-file-name "~/code/signel")))
-(require 'signel)
-
-(require 'signal-config)
-
-;;; cj/signal--jstr
-
-(ert-deftest test-signal-config-jstr-string ()
- "Normal: a non-blank string passes through unchanged."
- (should (equal (cj/signal--jstr "hi") "hi")))
-
-(ert-deftest test-signal-config-jstr-rejects-nonstrings ()
- "Boundary/Error: nil, empty, whitespace, and non-string sentinels become nil."
- (should-not (cj/signal--jstr nil))
- (should-not (cj/signal--jstr ""))
- (should-not (cj/signal--jstr " "))
- (should-not (cj/signal--jstr :null))
- (should-not (cj/signal--jstr 42)))
-
-;;; cj/signal--contact-display-name
-;; Field priority: nickName, then nickGiven+nickFamily, then top-level name,
-;; then top-level given+family, then profile given+family, then username.
-
-(ert-deftest test-signal-config-display-name-prefers-name ()
- "Normal: the top-level combined name wins over the given/family parts."
- (should (equal (cj/signal--contact-display-name
- '((name . "Alice Anderson") (givenName . "Ali") (familyName . "A")))
- "Alice Anderson")))
-
-(ert-deftest test-signal-config-display-name-nickname-wins ()
- "Normal: a nickName overrides the contact name."
- (should (equal (cj/signal--contact-display-name
- '((nickName . "Edster") (name . "Eve Edwards")
- (givenName . "Eve") (familyName . "Edwards")))
- "Edster")))
-
-(ert-deftest test-signal-config-display-name-nickname-parts ()
- "Boundary: nickGivenName+nickFamilyName combine when nickName is unset."
- (should (equal (cj/signal--contact-display-name
- '((nickGivenName . "DJ") (nickFamilyName . "Cool") (name . "Daniel")))
- "DJ Cool")))
-
-(ert-deftest test-signal-config-display-name-toplevel-given-family ()
- "Boundary: with no name, top-level givenName+familyName combine."
- (should (equal (cj/signal--contact-display-name
- '((name) (givenName . "Bob") (familyName . "Brown")))
- "Bob Brown")))
-
-(ert-deftest test-signal-config-display-name-profile-fallback ()
- "Boundary: with no name or top-level parts, profile given/family is the fallback."
- (should (equal (cj/signal--contact-display-name
- '((name) (givenName) (familyName)
- (profile . ((givenName . "Carol") (familyName)))))
- "Carol")))
-
-(ert-deftest test-signal-config-display-name-username-fallback ()
- "Boundary: username is the last name source."
- (should (equal (cj/signal--contact-display-name
- '((name) (username . "dave.42")))
- "dave.42")))
-
-(ert-deftest test-signal-config-display-name-none ()
- "Error: no usable name yields nil."
- (should-not (cj/signal--contact-display-name
- '((name) (givenName) (familyName)
- (profile . ((givenName) (familyName))))))
- (should-not (cj/signal--contact-display-name '((number . "+15551112222")))))
-
-;;; cj/signal--parse-contacts
-
-(defconst test-signal-config--contacts-json
- "[
- {\"number\":\"+15551112222\",\"uuid\":\"uuid-a\",\"name\":\"Alice Anderson\",\"givenName\":\"Alice\",\"familyName\":\"Anderson\",\"nickName\":null,\"nickGivenName\":null,\"nickFamilyName\":null,\"username\":null,\"profile\":{\"givenName\":null,\"familyName\":null}},
- {\"number\":\"+15553334444\",\"uuid\":null,\"name\":null,\"givenName\":\"Bob\",\"familyName\":\"Brown\",\"nickName\":null,\"username\":null,\"profile\":{\"givenName\":null,\"familyName\":null}},
- {\"number\":\"+15555556666\",\"uuid\":\"uuid-c\",\"name\":null,\"givenName\":null,\"familyName\":null,\"nickName\":null,\"username\":null,\"profile\":{\"givenName\":\"Carol\",\"familyName\":null}},
- {\"number\":null,\"uuid\":\"uuid-d\",\"name\":null,\"givenName\":null,\"familyName\":null,\"username\":null,\"profile\":{\"givenName\":null,\"familyName\":null}},
- {\"number\":\"+15557778888\",\"uuid\":\"uuid-e\",\"name\":\"Eve Edwards\",\"givenName\":\"Eve\",\"familyName\":\"Edwards\",\"nickName\":\"Edster\",\"username\":null,\"profile\":{\"givenName\":null,\"familyName\":null}}
-]"
- "Synthetic fixture mirroring the signal-cli 0.14.4.1 `listContacts' shape:
-top-level name/givenName/familyName and nickName fields, with a profile
-sub-object whose name fields are usually null. Field layout was confirmed
-against a live linked account on 2026-05-26; the values here are fake.")
-
-(ert-deftest test-signal-config-parse-contacts-normal ()
- "Normal: top-level name, top-level parts, profile fallback, uuid fallback, nickname."
- (let* ((result (json-read-from-string test-signal-config--contacts-json))
- (pairs (cj/signal--parse-contacts result)))
- (should (equal pairs
- '(("Alice Anderson (+15551112222)" . "+15551112222")
- ("Bob Brown (+15553334444)" . "+15553334444")
- ("Carol (+15555556666)" . "+15555556666")
- ("Edster (+15557778888)" . "+15557778888")
- ("uuid-d" . "uuid-d"))))))
-
-(ert-deftest test-signal-config-parse-contacts-empty ()
- "Boundary: an empty result yields nil for both vector and nil input."
- (should-not (cj/signal--parse-contacts []))
- (should-not (cj/signal--parse-contacts nil)))
-
-(ert-deftest test-signal-config-parse-contacts-accepts-list-and-vector ()
- "Boundary: vector and list result sequences parse identically."
- (let ((entry '((number . "+15551112222") (name . "Al"))))
- (should (equal (cj/signal--parse-contacts (vector entry))
- (cj/signal--parse-contacts (list entry))))))
-
-(ert-deftest test-signal-config-parse-contacts-drops-recipientless ()
- "Error: a contact with neither number nor uuid is dropped."
- (should-not (cj/signal--parse-contacts
- (list '((name . "Ghost") (number) (uuid))))))
-
-;;; cj/signal--suppress-notify-p
-
-(ert-deftest test-signal-config-suppress-when-viewing-focused ()
- "Normal: viewing the chat buffer with focus suppresses the notification."
- (should (cj/signal--suppress-notify-p
- "+15551112222" "*Signel: +15551112222*" t)))
-
-(ert-deftest test-signal-config-no-suppress-other-buffer ()
- "Boundary: a different selected buffer does not suppress."
- (should-not (cj/signal--suppress-notify-p
- "+15551112222" "*scratch*" t)))
-
-(ert-deftest test-signal-config-no-suppress-unfocused ()
- "Boundary: viewing the chat but with the frame unfocused still notifies."
- (should-not (cj/signal--suppress-notify-p
- "+15551112222" "*Signel: +15551112222*" nil)))
-
-(ert-deftest test-signal-config-no-suppress-nil-viewing ()
- "Error: a nil viewing-buffer name does not suppress."
- (should-not (cj/signal--suppress-notify-p "+15551112222" nil t)))
-
-;;; cj/signel--ensure-started
-
-(ert-deftest test-signal-config-ensure-started-live-process-noop ()
- "Normal: with a live signel process, ensure-started returns without
-calling `signel-start' or the pre-warm fetch."
- (let ((start-called nil)
- (fetch-called nil))
- (cl-letf (((symbol-function 'process-live-p) (lambda (_) t))
- ((symbol-function 'get-process) (lambda (_) 'fake-proc))
- ((symbol-function 'signel-start)
- (lambda () (setq start-called t)))
- ((symbol-function 'cj/signel--fetch-contacts)
- (lambda (&rest _) (setq fetch-called t))))
- (cj/signel--ensure-started)
- (should-not start-called)
- (should-not fetch-called))))
-
-(ert-deftest test-signal-config-ensure-started-starts-when-account-set ()
- "Normal: with `signel-account' set and no live process, ensure-started
-calls `signel-start' to bring the daemon up."
- (let ((start-called nil)
- (signel-account "+15555550100"))
- (cl-letf (((symbol-function 'process-live-p) (lambda (_) nil))
- ((symbol-function 'get-process) (lambda (_) nil))
- ((symbol-function 'signel-start)
- (lambda () (setq start-called t)))
- ((symbol-function 'cj/signel--fetch-contacts)
- (lambda (&rest _) nil)))
- (cj/signel--ensure-started)
- (should start-called))))
-
-(ert-deftest test-signal-config-ensure-started-prewarms-on-start ()
- "Normal: when ensure-started actually starts the daemon, it triggers a
-pre-warm fetch so the picker cache is warm on first invocation."
- (let ((fetch-called nil)
- (signel-account "+15555550100"))
- (cl-letf (((symbol-function 'process-live-p) (lambda (_) nil))
- ((symbol-function 'get-process) (lambda (_) nil))
- ((symbol-function 'signel-start) (lambda () nil))
- ((symbol-function 'cj/signel--fetch-contacts)
- (lambda (&rest _) (setq fetch-called t))))
- (cj/signel--ensure-started)
- (should fetch-called))))
-
-(ert-deftest test-signal-config-ensure-started-errors-when-no-account ()
- "Error: with `signel-account' nil, ensure-started signals a user-error
-naming the remedy (set the account in the private config) instead of
-starting an account-less daemon."
- (let ((signel-account nil))
- (cl-letf (((symbol-function 'process-live-p) (lambda (_) nil))
- ((symbol-function 'get-process) (lambda (_) nil)))
- (should-error (cj/signel--ensure-started) :type 'user-error))))
-
-(ert-deftest test-signal-config-ensure-started-requires-signel-first ()
- "Error: ensure-started must `require' signel BEFORE reading any of
-its private variables, so a `void-variable' error cannot fire when
-called before signel has been autoloaded. Captures the bug where
-`signel--process-name' was forward-declared in signal-config but not
-yet bound at runtime because the `use-package' autoload had not fired.
-
-Asserts ordering, not just presence: a future refactor that moves the
-`require' below the `cond' would still execute the require eventually
-but the variable read in the cond would fire void-variable first.
-The test fails if `require' isn't the first call inside the function."
- (let ((call-order nil))
- (cl-letf (((symbol-function 'require)
- (lambda (feature &optional _filename _noerror)
- (push (list 'require feature) call-order)
- t))
- ((symbol-function 'process-live-p)
- (lambda (_) (push 'process-live-p call-order) t))
- ((symbol-function 'get-process)
- (lambda (_) (push 'get-process call-order) 'fake-proc)))
- (cj/signel--ensure-started)
- (should (equal (car (reverse call-order)) '(require signel))))))
-
-;;; cj/signel--fetch-contacts + cj/signel--contact-cache
-
-(ert-deftest test-signal-config-fetch-contacts-issues-list-contacts-rpc ()
- "Normal: fetch-contacts sends a `listContacts' RPC and registers a
-success callback so the response routes back."
- (let (sent-method sent-callback)
- (cl-letf (((symbol-function 'signel--send-rpc)
- (lambda (method _params _target callback)
- (setq sent-method method
- sent-callback callback)
- 1)))
- (cj/signel--fetch-contacts))
- (should (equal sent-method "listContacts"))
- (should (functionp sent-callback))))
-
-(ert-deftest test-signal-config-fetch-contacts-callback-populates-cache ()
- "Normal: on a successful result, the callback parses the contact list
-and stores the (LABEL . RECIPIENT) alist in `cj/signel--contact-cache'."
- (let (sent-callback)
- (cl-letf (((symbol-function 'signel--send-rpc)
- (lambda (_method _params _target callback)
- (setq sent-callback callback) 1)))
- (setq cj/signel--contact-cache nil)
- (cj/signel--fetch-contacts)
- (funcall sent-callback
- [((number . "+15555550100") (givenName . "Alice"))]))
- (should (equal cj/signel--contact-cache
- '(("Alice (+15555550100)" . "+15555550100"))))))
-
-(ert-deftest test-signal-config-fetch-contacts-empty-result-caches-sentinel ()
- "Boundary: an empty listContacts result caches the `empty' sentinel.
-nil would read as a cold cache and re-run the picker's blocking fetch on
-every open; the sentinel marks warm-but-empty. Still distinct from a
-failure path, which never invokes the success callback. (This test
-formerly pinned the nil behavior -- the 2026-06 audit's F2 bug.)"
- (let (sent-callback)
- (cl-letf (((symbol-function 'signel--send-rpc)
- (lambda (_method _params _target callback)
- (setq sent-callback callback) 1)))
- (setq cj/signel--contact-cache '(("stale" . "+10000000000")))
- (cj/signel--fetch-contacts)
- (funcall sent-callback []))
- (should (eq cj/signel--contact-cache 'empty))))
-
-;;; cj/signel-refresh-contacts
-
-(ert-deftest test-signal-config-refresh-contacts-clears-and-refetches ()
- "Normal: `cj/signel-refresh-contacts' clears the cache and triggers a
-fresh fetch so a stale entry can't survive a user-driven refresh."
- (let ((fetch-called nil))
- (setq cj/signel--contact-cache '(("stale" . "+10000000000")))
- (cl-letf (((symbol-function 'cj/signel--fetch-contacts)
- (lambda (&rest _) (setq fetch-called t))))
- (cj/signel-refresh-contacts))
- (should-not cj/signel--contact-cache)
- (should fetch-called)))
-
-;;; cj/signel-message picker
-
-(ert-deftest test-signal-config-message-warm-cache-picks-contact ()
- "Normal: with a warm cache, picking a contact label opens that
-recipient's chat buffer."
- (let ((chosen-recipient nil)
- (signel-account "+15555550100"))
- (setq cj/signel--contact-cache
- '(("Alice (+15555550200)" . "+15555550200")))
- (cl-letf (((symbol-function 'cj/signel--ensure-started) (lambda () nil))
- ((symbol-function 'completing-read)
- (lambda (&rest _) "Alice (+15555550200)"))
- ((symbol-function 'signel-chat)
- (lambda (r) (setq chosen-recipient r))))
- (cj/signel-message))
- (should (equal chosen-recipient "+15555550200"))))
-
-(ert-deftest test-signal-config-message-warm-cache-picks-note-to-self ()
- "Normal: the pinned `Note to Self' entry resolves to `signel-account'
-so a self-message lands in the Signal Note-to-Self thread."
- (let ((chosen-recipient nil)
- (signel-account "+15555550100"))
- (setq cj/signel--contact-cache
- '(("Alice (+15555550200)" . "+15555550200")))
- (cl-letf (((symbol-function 'cj/signel--ensure-started) (lambda () nil))
- ((symbol-function 'completing-read)
- (lambda (&rest _) "Note to Self"))
- ((symbol-function 'signel-chat)
- (lambda (r) (setq chosen-recipient r))))
- (cj/signel-message))
- (should (equal chosen-recipient "+15555550100"))))
-
-(ert-deftest test-signal-config-message-cold-cache-fetch-resolves-in-time ()
- "Normal: cold cache, fetch's after-callback fires inside the bounded
-wait, picker proceeds with the now-warm cache."
- (let ((chosen-recipient nil)
- (signel-account "+15555550100")
- (cj/signel-fetch-timeout 1.0))
- (setq cj/signel--contact-cache nil)
- (cl-letf (((symbol-function 'cj/signel--ensure-started) (lambda () nil))
- ((symbol-function 'cj/signel--fetch-contacts)
- (lambda (&optional after-cb)
- (setq cj/signel--contact-cache
- '(("Bob (+15555550300)" . "+15555550300")))
- (when after-cb (funcall after-cb))))
- ((symbol-function 'completing-read)
- (lambda (&rest _) "Bob (+15555550300)"))
- ((symbol-function 'signel-chat)
- (lambda (r) (setq chosen-recipient r))))
- (cj/signel-message))
- (should (equal chosen-recipient "+15555550300"))))
-
-(ert-deftest test-signal-config-message-cold-cache-timeout-errors ()
- "Error: cold cache, fetch never resolves, picker user-errors before
-the bounded wait would let Emacs hang on a dead daemon."
- (let ((signel-account "+15555550100")
- (cj/signel-fetch-timeout 0.1))
- (setq cj/signel--contact-cache nil)
- (cl-letf (((symbol-function 'cj/signel--ensure-started) (lambda () nil))
- ((symbol-function 'cj/signel--fetch-contacts)
- (lambda (&rest _) nil)))
- (should-error (cj/signel-message) :type 'user-error))))
-
-;;; cj/signel-message-self
-
-(ert-deftest test-signal-config-message-self-calls-signel-chat-with-account ()
- "Normal: the direct self-message command opens a chat buffer addressed
-to `signel-account', skipping the picker entirely."
- (let ((chosen-recipient nil)
- (signel-account "+15555550100"))
- (cl-letf (((symbol-function 'cj/signel--ensure-started) (lambda () nil))
- ((symbol-function 'signel-chat)
- (lambda (r) (setq chosen-recipient r))))
- (cj/signel-message-self))
- (should (equal chosen-recipient "+15555550100"))))
-
-;;; cj/signel-prefix-map (C-; M)
-
-(ert-deftest test-signal-config-prefix-map-has-expected-bindings ()
- "Normal: the signel C-; M prefix map binds m / s / d / q / SPC to the
-commands the workflow spec names."
- (should (eq (keymap-lookup cj/signel-prefix-map "m")
- #'cj/signel-message))
- (should (eq (keymap-lookup cj/signel-prefix-map "s")
- #'cj/signel-message-self))
- (should (eq (keymap-lookup cj/signel-prefix-map "d")
- #'signel-dashboard))
- (should (eq (keymap-lookup cj/signel-prefix-map "q")
- #'signel-stop))
- (should (eq (keymap-lookup cj/signel-prefix-map "SPC")
- #'cj/signel-connect)))
-
-(ert-deftest test-signal-config-prefix-map-registered-under-c-semi-m ()
- "Normal: loading signal-config registers `cj/signel-prefix-map' under
-`M' in `cj/custom-keymap', so C-; M reaches the signel prefix. Guards
-the wiring contract that the load-order bug broke: signal-config must
-register through `cj/register-prefix-map', not a boundp-guarded direct
-mutation that silently no-ops when keybindings loaded in a different
-order."
- (require 'keybindings)
- (should (eq (keymap-lookup cj/custom-keymap "M") cj/signel-prefix-map)))
-
-;;; display-buffer-alist entry for *Signel: ...* chat buffers
-
-(ert-deftest test-signal-config-chat-buffer-display-rule-uses-bottom-30 ()
- "Normal: signal-config registers a `display-buffer-alist' entry that
-matches `*Signel: <id>*' buffers, routes them through
-`display-buffer-at-bottom', and sets `window-height' to 0.3 so the
-chat docks to the bottom 30% of the frame."
- (let ((entry (seq-find (lambda (e) (equal (car e) "\\`\\*Signel: "))
- display-buffer-alist)))
- (should entry)
- (should (memq 'display-buffer-at-bottom (cadr entry)))
- (should (equal 0.3 (cdr (assq 'window-height (cddr entry)))))))
-
-(ert-deftest test-signal-config-chat-buffer-display-rule-matches-buffer-name ()
- "Boundary: the registered regex matches a realistic chat buffer name
-\(phone-number id and group-id) and does not match unrelated buffers."
- (let* ((entry (seq-find (lambda (e) (equal (car e) "\\`\\*Signel: "))
- display-buffer-alist))
- (regex (car entry)))
- (should regex)
- (should (string-match-p regex "*Signel: +15555550100*"))
- (should (string-match-p regex "*Signel: groupid-abc*"))
- (should-not (string-match-p regex "*signel-log*"))
- (should-not (string-match-p regex "scratch"))))
-
-(provide 'test-signal-config)
-;;; test-signal-config.el ends here
diff --git a/tests/test-signel-cancel-input.el b/tests/test-signel-cancel-input.el
deleted file mode 100644
index b2a7ef89..00000000
--- a/tests/test-signel-cancel-input.el
+++ /dev/null
@@ -1,74 +0,0 @@
-;;; test-signel-cancel-input.el --- Cancel-input contract for the signel fork -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; `signel--cancel-input' is the C-c C-k handler in `signel-chat-mode'.
-;; Its contract: clear any in-progress input between `signel--input-marker'
-;; and `point-max' (so the prompt is fresh on next visit), then dismiss
-;; the window via `quit-window' (the buffer stays alive so chat history
-;; survives revisits). These tests lock the contract; the binding test
-;; locks the keymap entry.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-(eval-and-compile
- (add-to-list 'load-path (expand-file-name "~/code/signel")))
-(require 'signel)
-
-(defmacro test-signel-cancel--with-chat-buffer (&rest body)
- "Set up a temp signel-chat-mode buffer with prompt drawn and run BODY."
- (declare (indent 0))
- `(with-temp-buffer
- (signel-chat-mode)
- (setq signel--chat-id "+15555550100")
- (signel--draw-prompt)
- ,@body))
-
-(ert-deftest test-signel-cancel-input-clears-pending-text ()
- "Normal: pending input from input-marker to point-max is cleared."
- (test-signel-cancel--with-chat-buffer
- (insert "abandoned-draft")
- (cl-letf (((symbol-function 'quit-window) (lambda (&rest _) nil)))
- (signel--cancel-input))
- (should-not (signel--pending-input))))
-
-(ert-deftest test-signel-cancel-input-empty-input-area-is-a-noop ()
- "Boundary: cancelling with no in-progress input is harmless."
- (test-signel-cancel--with-chat-buffer
- (cl-letf (((symbol-function 'quit-window) (lambda (&rest _) nil)))
- (signel--cancel-input))
- (should-not (signel--pending-input))))
-
-(ert-deftest test-signel-cancel-input-calls-quit-window ()
- "Normal: cancel dismisses the window via `quit-window'."
- (test-signel-cancel--with-chat-buffer
- (insert "abandoned-draft")
- (let ((called nil))
- (cl-letf (((symbol-function 'quit-window)
- (lambda (&rest _) (setq called t))))
- (signel--cancel-input))
- (should called))))
-
-(ert-deftest test-signel-cancel-input-preserves-buffer ()
- "Normal: cancel does not kill the buffer; chat history (prompt + prior
-content above the input marker) survives so reopening the contact lands
-in the same buffer."
- (test-signel-cancel--with-chat-buffer
- (insert "abandoned-draft")
- (let ((buf (current-buffer)))
- (cl-letf (((symbol-function 'quit-window) (lambda (&rest _) nil)))
- (signel--cancel-input))
- (should (buffer-live-p buf)))))
-
-(ert-deftest test-signel-chat-mode-binds-c-c-c-k-to-cancel ()
- "Normal: `signel-chat-mode' binds C-c C-k to `signel--cancel-input' so
-the documented cancel gesture reaches the handler."
- (with-temp-buffer
- (signel-chat-mode)
- (should (eq (lookup-key (current-local-map) (kbd "C-c C-k"))
- #'signel--cancel-input))))
-
-(provide 'test-signel-cancel-input)
-;;; test-signel-cancel-input.el ends here
diff --git a/tests/test-signel-input-preservation.el b/tests/test-signel-input-preservation.el
deleted file mode 100644
index e8ce4ddb..00000000
--- a/tests/test-signel-input-preservation.el
+++ /dev/null
@@ -1,68 +0,0 @@
-;;; test-signel-input-preservation.el --- Regression for signel #2 input clobber -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; signel-chat-mode buffers have an editable prompt area starting at
-;; `signel--input-marker'. Before this fix, both `signel--insert-msg' (the
-;; receive path) and `signel--insert-system-msg' (the RPC-error path)
-;; called `(delete-region (point) (point-max))' to clear the old prompt
-;; before redrawing it, which destroyed any text the user was mid-typing.
-;;
-;; These tests lock the preservation contract: a small `signel--pending-input'
-;; helper captures the in-progress input from the marker to `point-max', and
-;; both inserters restore it after the freshly drawn prompt. The chat-mode
-;; buffer is constructed in a temp buffer; `signel--insert-msg' is steered
-;; to it via a stub on `signel--get-buffer'.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-(eval-and-compile
- (add-to-list 'load-path (expand-file-name "~/code/signel")))
-(require 'signel)
-
-(defmacro test-signel--with-chat-buffer (&rest body)
- "Set up a temp signel-chat-mode buffer with prompt drawn and run BODY."
- (declare (indent 0))
- `(with-temp-buffer
- (signel-chat-mode)
- (setq signel--chat-id "+15555550100")
- (signel--draw-prompt)
- ,@body))
-
-(ert-deftest test-signel-input-pending-returns-typed-text ()
- "Normal: with text after the prompt marker, `signel--pending-input'
-returns the captured text."
- (test-signel--with-chat-buffer
- (insert "halfwritten")
- (should (equal (signel--pending-input) "halfwritten"))))
-
-(ert-deftest test-signel-input-pending-returns-nil-when-empty ()
- "Boundary: an empty input area returns nil so callers don't restore an
-empty string after the prompt."
- (test-signel--with-chat-buffer
- (should-not (signel--pending-input))))
-
-(ert-deftest test-signel-input-system-msg-preserves-pending-input ()
- "Regression for #2: `signel--insert-system-msg' redraws the prompt
-without clobbering text the user was mid-typing."
- (test-signel--with-chat-buffer
- (insert "halfwritten")
- (signel--insert-system-msg "An error happened" 'signel-error-face)
- (should (string-match-p "An error happened" (buffer-string)))
- (should (equal (signel--pending-input) "halfwritten"))))
-
-(ert-deftest test-signel-input-msg-preserves-pending-input ()
- "Regression for #2: `signel--insert-msg' (the receive path) redraws the
-prompt without clobbering the user's in-progress input."
- (test-signel--with-chat-buffer
- (insert "halfwritten")
- (cl-letf (((symbol-function 'signel--get-buffer)
- (lambda (_) (current-buffer))))
- (signel--insert-msg "+15555550100" "Alice" "Hi there" nil nil nil))
- (should (string-match-p "Hi there" (buffer-string)))
- (should (equal (signel--pending-input) "halfwritten"))))
-
-(provide 'test-signel-input-preservation)
-;;; test-signel-input-preservation.el ends here
diff --git a/tests/test-signel-notify-function.el b/tests/test-signel-notify-function.el
deleted file mode 100644
index e3d97af5..00000000
--- a/tests/test-signel-notify-function.el
+++ /dev/null
@@ -1,89 +0,0 @@
-;;; test-signel-notify-function.el --- Tests for signel's notify-function dispatch -*- lexical-binding: t -*-
-
-;;; Commentary:
-;; signel's receive handler (signel.el in the fork at ~/code/signel)
-;; raised notifications through a hardwired `notifications-notify'
-;; call. The notification slice (docs/specs/signal-client-spec-doing.org,
-;; "Notification slice" addendum) replaces that with
-;; `signel-notify-function', a customization point called with
-;; CHAT-ID, SENDER, and BODY so a config layer can add suppression or
-;; route through an external notifier. These tests cover the
-;; dispatch: text, sticker, and attachment bodies reach the function
-;; with the right arguments, and the default preserves the plain
-;; `notifications-notify' behavior.
-;;
-;; `signel--handle-receive' is exercised directly with synthetic
-;; envelope alists; buffer/dashboard side effects are stubbed. No
-;; live process needed.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-(eval-and-compile
- (add-to-list 'load-path (expand-file-name "~/code/signel")))
-(require 'signel)
-
-(defun test-signel-notify--receive (envelope)
- "Run `signel--handle-receive' on ENVELOPE, capturing notify calls.
-Returns the list of (CHAT-ID SENDER BODY) argument lists the handler
-passed to `signel-notify-function', oldest first. Buffer and
-dashboard side effects are stubbed out."
- (let (calls)
- (cl-letf (((symbol-function 'signel--insert-msg) (lambda (&rest _) nil))
- ((symbol-function 'signel--dashboard-refresh) (lambda () nil))
- ((symbol-function 'signel--get-buffer)
- (lambda (_) (current-buffer))))
- (let ((signel-notify-function
- (lambda (chat-id sender body)
- (push (list chat-id sender body) calls)))
- (signel-auto-open-buffer nil))
- (signel--handle-receive `((envelope . ,envelope)))))
- (nreverse calls)))
-
-(ert-deftest test-signel-notify-function-text-message ()
- "Normal: a text dataMessage calls the function with chat-id, sender, text."
- (should (equal (test-signel-notify--receive
- '((sourceNumber . "+15551234567")
- (sourceName . "Alice")
- (dataMessage . ((message . "hi there")))))
- '(("+15551234567" "Alice" "hi there")))))
-
-(ert-deftest test-signel-notify-function-sticker-placeholder ()
- "Boundary: a sticker with no text gets the [Sticker] placeholder body."
- (should (equal (test-signel-notify--receive
- '((sourceNumber . "+15551234567")
- (sourceName . "Alice")
- (dataMessage . ((sticker . ((packId . "p1")))))))
- '(("+15551234567" "Alice" "[Sticker]")))))
-
-(ert-deftest test-signel-notify-function-attachment-placeholder ()
- "Boundary: an attachment with no text gets the [Attachment] placeholder."
- (should (equal (test-signel-notify--receive
- '((sourceNumber . "+15551234567")
- (sourceName . "Alice")
- (dataMessage . ((attachments . [((id . "a1"))])))))
- '(("+15551234567" "Alice" "[Attachment]")))))
-
-(ert-deftest test-signel-notify-function-no-data-no-call ()
- "Boundary: an envelope with no dataMessage never calls the function."
- (should-not (test-signel-notify--receive
- '((sourceNumber . "+15551234567")
- (sourceName . "Alice")
- (typingMessage . ((action . "STARTED")))))))
-
-(ert-deftest test-signel-notify-function-default-preserves-behavior ()
- "Normal: the default value raises a plain notifications-notify toast."
- (should (eq signel-notify-function #'signel--notify-default))
- (let (calls)
- (cl-letf (((symbol-function 'notifications-notify)
- (lambda (&rest args) (push args calls) nil)))
- (signel--notify-default "+15551234567" "Alice" "hi"))
- (should (= (length calls) 1))
- (let ((args (car calls)))
- (should (equal (plist-get args :title) "Signel: Alice"))
- (should (equal (plist-get args :body) "hi")))))
-
-(provide 'test-signel-notify-function)
-;;; test-signel-notify-function.el ends here
diff --git a/tests/test-signel-rpc-dispatch.el b/tests/test-signel-rpc-dispatch.el
deleted file mode 100644
index 5ae023d6..00000000
--- a/tests/test-signel-rpc-dispatch.el
+++ /dev/null
@@ -1,94 +0,0 @@
-;;; test-signel-rpc-dispatch.el --- Tests for signel JSON-RPC success-result dispatch -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; signel's JSON-RPC dispatch (signel.el in the fork at ~/code/signel) routes
-;; incoming `receive' notifications and errors, but successful
-;; `((id . N) (result . VALUE))' responses had no path until this work added a
-;; request-callback table. These tests cover the new behavior: a registered
-;; callback fires with the result and is then removed; an error response
-;; also removes the handler so a retry starts clean; an unregistered id is a
-;; silent no-op; passing SUCCESS-CALLBACK to `signel--send-rpc' registers it
-;; under the returned id.
-;;
-;; The dispatch tests exercise `signel--dispatch' directly with synthetic JSON
-;; alists; no live process is needed. The send-rpc test stubs `get-process'
-;; and `process-send-string' so it doesn't require a running signal-cli.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-(eval-and-compile
- (add-to-list 'load-path (expand-file-name "~/code/signel")))
-(require 'signel)
-
-(defun test-signel-rpc--reset ()
- "Reset signel dispatch state to a clean baseline before each test."
- (clrhash signel--request-handler-map)
- (clrhash signel--request-buffer-map)
- (setq signel--rpc-id-counter 0))
-
-(ert-deftest test-signel-rpc-dispatch-result-invokes-callback ()
- "Normal: a result response with a registered id fires the callback with the
-result value and removes the handler."
- (test-signel-rpc--reset)
- (let ((captured nil))
- (puthash 7 (lambda (val) (setq captured val)) signel--request-handler-map)
- (signel--dispatch '((jsonrpc . "2.0") (id . 7)
- (result . ((contacts . [1 2 3])))))
- (should (equal captured '((contacts . [1 2 3]))))
- (should-not (gethash 7 signel--request-handler-map))))
-
-(ert-deftest test-signel-rpc-dispatch-unknown-id-is-noop ()
- "Boundary: a result response with an unregistered id is a silent no-op:
-neither receive nor error handler fires, and the handler map stays empty."
- (test-signel-rpc--reset)
- (let ((called nil))
- (cl-letf (((symbol-function 'signel--handle-error)
- (lambda (&rest _) (setq called 'error)))
- ((symbol-function 'signel--handle-receive)
- (lambda (&rest _) (setq called 'receive))))
- (signel--dispatch '((jsonrpc . "2.0") (id . 99) (result . "anything"))))
- (should-not called)
- (should (zerop (hash-table-count signel--request-handler-map)))))
-
-(ert-deftest test-signel-rpc-dispatch-error-cleans-up-handler ()
- "Error: an error response with a registered id removes the handler without
-firing the callback, leaving the map clean for a retry."
- (test-signel-rpc--reset)
- (let ((fired nil))
- (puthash 11 (lambda (&rest _) (setq fired t))
- signel--request-handler-map)
- (cl-letf (((symbol-function 'signel--handle-error) (lambda (&rest _) nil)))
- (signel--dispatch '((jsonrpc . "2.0") (id . 11)
- (error . ((code . -1) (message . "boom"))))))
- (should-not fired)
- (should-not (gethash 11 signel--request-handler-map))))
-
-(ert-deftest test-signel-rpc-send-rpc-registers-success-callback ()
- "Normal: passing a SUCCESS-CALLBACK to `signel--send-rpc' stores it under
-the returned id so the matching response can route to it."
- (test-signel-rpc--reset)
- (let ((cb (lambda (_) 'ok))
- (sent nil))
- (cl-letf (((symbol-function 'get-process) (lambda (&rest _) 'fake-proc))
- ((symbol-function 'process-send-string)
- (lambda (_ s) (setq sent s)))
- ((symbol-function 'signel--log) (lambda (&rest _) nil)))
- (let ((id (signel--send-rpc "listContacts" nil nil cb)))
- (should (eq cb (gethash id signel--request-handler-map)))
- (should (stringp sent))))))
-
-(ert-deftest test-signel-rpc-stop-clears-handler-map ()
- "Normal (reconnect-invalidation): `signel-stop' clears the handler map so a
-restart starts with no stale callbacks waiting for responses that will never
-arrive."
- (test-signel-rpc--reset)
- (puthash 13 (lambda (&rest _) nil) signel--request-handler-map)
- (cl-letf (((symbol-function 'get-process) (lambda (&rest _) nil)))
- (signel-stop))
- (should (zerop (hash-table-count signel--request-handler-map))))
-
-(provide 'test-signel-rpc-dispatch)
-;;; test-signel-rpc-dispatch.el ends here