diff options
Diffstat (limited to 'archive/tests')
| -rw-r--r-- | archive/tests/test-show-kill-ring--insert-item.el | 73 | ||||
| -rw-r--r-- | archive/tests/test-signal-config--contact-cache.el | 61 | ||||
| -rw-r--r-- | archive/tests/test-signal-config-notify.el | 150 | ||||
| -rw-r--r-- | archive/tests/test-signal-config.el | 410 | ||||
| -rw-r--r-- | archive/tests/test-signel-cancel-input.el | 74 | ||||
| -rw-r--r-- | archive/tests/test-signel-input-preservation.el | 68 | ||||
| -rw-r--r-- | archive/tests/test-signel-notify-function.el | 89 | ||||
| -rw-r--r-- | archive/tests/test-signel-rpc-dispatch.el | 94 |
8 files changed, 1019 insertions, 0 deletions
diff --git a/archive/tests/test-show-kill-ring--insert-item.el b/archive/tests/test-show-kill-ring--insert-item.el new file mode 100644 index 00000000..a29ca75e --- /dev/null +++ b/archive/tests/test-show-kill-ring--insert-item.el @@ -0,0 +1,73 @@ +;;; test-show-kill-ring--insert-item.el --- Tests for show-kill-insert-item -*- lexical-binding: t -*- + +;;; Commentary: +;; Tests for `show-kill-insert-item' in show-kill-ring.el — inserts a +;; kill-ring entry into the current buffer, truncating to +;; `show-kill-max-item-size' with an ellipsis when too long. The ellipsis +;; sits inline for short items and on its own line for items wider than the +;; frame. Frame width is read at runtime so the test is environment-stable. + +;;; Code: + +(require 'ert) +(require 'show-kill-ring) + +;;; Normal Cases + +(ert-deftest test-show-kill-ring-insert-item-short-verbatim () + "Normal: an item shorter than the max is inserted unchanged." + (let ((show-kill-max-item-size 1000)) + (with-temp-buffer + (show-kill-insert-item "hello") + (should (string= (buffer-string) "hello"))))) + +(ert-deftest test-show-kill-ring-insert-item-inline-ellipsis () + "Normal: an over-max item narrower than the frame gets an inline ellipsis." + (let* ((show-kill-max-item-size 5) + (len (/ (frame-width) 2)) ; > max, < (frame-width - 5) + (item (make-string len ?b))) + (with-temp-buffer + (show-kill-insert-item item) + (should (string= (buffer-string) "bbbbb..."))))) + +;;; Boundary Cases + +(ert-deftest test-show-kill-ring-insert-item-length-equals-max-truncates () + "Boundary: length exactly equal to max truncates — the guard is (< len max)." + (let ((show-kill-max-item-size 5)) + (with-temp-buffer + (show-kill-insert-item "hello") ; length 5, equals max + (should (string= (buffer-string) "hello..."))))) + +(ert-deftest test-show-kill-ring-insert-item-wide-newline-ellipsis () + "Boundary: an item wider than the frame puts the ellipsis on its own line." + (let* ((show-kill-max-item-size 5) + (item (make-string (+ (frame-width) 10) ?a))) + (with-temp-buffer + (show-kill-insert-item item) + (should (string= (buffer-string) "aaaaa\n..."))))) + +(ert-deftest test-show-kill-ring-insert-item-max-nil-verbatim () + "Boundary: a non-numeric max disables truncation." + (let ((show-kill-max-item-size nil)) + (with-temp-buffer + (show-kill-insert-item "anything long enough to exceed nothing") + (should (string= (buffer-string) + "anything long enough to exceed nothing"))))) + +(ert-deftest test-show-kill-ring-insert-item-max-negative-verbatim () + "Boundary: a negative max disables truncation." + (let ((show-kill-max-item-size -1)) + (with-temp-buffer + (show-kill-insert-item "abc") + (should (string= (buffer-string) "abc"))))) + +(ert-deftest test-show-kill-ring-insert-item-empty-string () + "Boundary: an empty item inserts nothing and does not error." + (let ((show-kill-max-item-size 1000)) + (with-temp-buffer + (show-kill-insert-item "") + (should (string= (buffer-string) ""))))) + +(provide 'test-show-kill-ring--insert-item) +;;; test-show-kill-ring--insert-item.el ends here diff --git a/archive/tests/test-signal-config--contact-cache.el b/archive/tests/test-signal-config--contact-cache.el new file mode 100644 index 00000000..720e0c3a --- /dev/null +++ b/archive/tests/test-signal-config--contact-cache.el @@ -0,0 +1,61 @@ +;;; 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/archive/tests/test-signal-config-notify.el b/archive/tests/test-signal-config-notify.el new file mode 100644 index 00000000..1a772289 --- /dev/null +++ b/archive/tests/test-signal-config-notify.el @@ -0,0 +1,150 @@ +;;; 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/archive/tests/test-signal-config.el b/archive/tests/test-signal-config.el new file mode 100644 index 00000000..f8bf4410 --- /dev/null +++ b/archive/tests/test-signal-config.el @@ -0,0 +1,410 @@ +;;; 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/archive/tests/test-signel-cancel-input.el b/archive/tests/test-signel-cancel-input.el new file mode 100644 index 00000000..b2a7ef89 --- /dev/null +++ b/archive/tests/test-signel-cancel-input.el @@ -0,0 +1,74 @@ +;;; 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/archive/tests/test-signel-input-preservation.el b/archive/tests/test-signel-input-preservation.el new file mode 100644 index 00000000..e8ce4ddb --- /dev/null +++ b/archive/tests/test-signel-input-preservation.el @@ -0,0 +1,68 @@ +;;; 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/archive/tests/test-signel-notify-function.el b/archive/tests/test-signel-notify-function.el new file mode 100644 index 00000000..e3d97af5 --- /dev/null +++ b/archive/tests/test-signel-notify-function.el @@ -0,0 +1,89 @@ +;;; 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/archive/tests/test-signel-rpc-dispatch.el b/archive/tests/test-signel-rpc-dispatch.el new file mode 100644 index 00000000..5ae023d6 --- /dev/null +++ b/archive/tests/test-signel-rpc-dispatch.el @@ -0,0 +1,94 @@ +;;; 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 |
