]> git.eshelyaron.com Git - emacs.git/commitdiff
Move experimental module querypoll to erc-notify
authorF. Jason Park <jp@neverwas.me>
Mon, 14 Oct 2024 05:45:05 +0000 (22:45 -0700)
committerEshel Yaron <me@eshelyaron.com>
Tue, 22 Oct 2024 18:55:00 +0000 (20:55 +0200)
* etc/ERC-NEWS: Announce migration of misplaced `querypoll' module from
erc-goodies.el to erc-notify.el.
* lisp/erc/erc-goodies.el: Move all definitions associated with
experimental module `querypoll' to erc-notify.
* lisp/erc/erc-notify.el (erc--querypoll-ring)
(erc--querypoll-timer, erc-querypoll-exclude-regexp)
(erc-querypoll-mode, erc-querypoll-enable, erc-querypoll-disable)
(erc--queries-current-p, erc-querypoll-period-params)
(erc--querypoll-compute-period, erc--querypoll-target-in-chan-p)
(erc--querypoll-get-length, erc--querypoll-get-next)
(erc--querypoll-subscribe, erc--querypoll-on-352)
(erc--querypoll-send): Move here from erc-goodies.
* test/lisp/erc/erc-goodies-tests.el (erc--querypoll-compute-period)
(erc--querypoll-target-in-chan-p, erc--querypoll-get-length)
(erc--querypoll-get-next): Move to new file erc-notify-tests.el.
* test/lisp/erc/erc-notify-tests.el: New file.  The `querypoll' module
was first introduced as part of bug#70928 in ERC 5.6.

(cherry picked from commit be3318baddbcb3ff607c784a14c1f21a70e392de)

etc/ERC-NEWS
lisp/erc/erc-goodies.el
lisp/erc/erc-notify.el
test/lisp/erc/erc-goodies-tests.el
test/lisp/erc/erc-notify-tests.el [new file with mode: 0644]

index 39403c174863b9dcb07ab4596027837dcbddaf63..3970f67d725eddf5ca754e51c122b84de3dc8608 100644 (file)
@@ -55,6 +55,13 @@ indicator stick with the most recent speaker's face, even when they're
 monologuing, instead of alternating between it and the highest ranked
 'erc-track-faces-normal-list' member in a given message.
 
+** Module 'querypoll' has left 'goodies' and moved in with 'notify'.
+The 'querypoll' module was initially placed in 'erc-goodies' even though
+a far more sensible home existed in 'erc-notify'.  Given the similarity
+of concerns and the newer module's "experimental" status, the migration
+was deemed worth any potential disruption, despite this being a point
+release.  ERC appreciates your understanding in this matter.
+
 \f
 * Changes in ERC 5.6
 
index ec1e0054dd5819cec674392b06b1423e0bb6fe57..28a9894525c4eeae1243501d2141a2af48b4602b 100644 (file)
@@ -1148,195 +1148,6 @@ servers.  If called from a program, PROC specifies the server process."
   (multi-occur (erc-buffer-list nil proc) string))
 
 
-;;;; querypoll
-
-(declare-function ring-empty-p "ring" (ring))
-(declare-function ring-insert "ring" (ring item))
-(declare-function ring-insert+extend "ring" (ring item))
-(declare-function ring-length "ring" (ring))
-(declare-function ring-member "ring" (ring item))
-(declare-function ring-ref "ring" (ring index))
-(declare-function ring-remove "ring" (ring &optional index))
-
-(defvar-local erc--querypoll-ring nil)
-(defvar-local erc--querypoll-timer nil)
-
-(defcustom erc-querypoll-exclude-regexp
-  (rx bot (or (: "*" (+ nonl)) (: (+ (in "A-Za-z")) "Serv")) eot)
-  "Pattern to skip polling for bots and services you regularly query."
-  :group 'erc
-  :package-version '(ERC . "5.6")
-  :type 'regexp)
-
-;;;###autoload(autoload 'erc-querypoll-mode "erc-goodies" nil t)
-(define-erc-module querypoll nil
-  "Send periodic \"WHO\" requests for each query buffer.
-Omit query participants who are currently present in some channel.
-Instead of announcing arrivals and departures, rely on other modules,
-like `nickbar', to provide UI feedback when changes occur.
-
-Once ERC implements the `monitor' extension, this module will serve as
-an optional fallback for keeping query-participant rolls up to date on
-servers that lack support or are stingy with their allotments.  Until
-such time, this module should be considered experimental.
-
-This is a local ERC module, so selectively polling only a subset of
-query targets is possible but cumbersome.  To do so, ensure
-`erc-querypoll-mode' is enabled in the server buffer, and then toggle it
-as appropriate in desired query buffers.  To stop polling for the
-current connection, toggle off the command \\[erc-querypoll-mode] from a
-server buffer, or run \\`M-x C-u erc-querypoll-disable RET' from a
-target buffer."
-  ((if erc--target
-       (if (erc-query-buffer-p)
-           (progn ; accommodate those who eschew `erc-modules'
-             (erc-with-server-buffer
-               (unless erc-querypoll-mode
-                 (erc-querypoll-mode +1)))
-             (erc--querypoll-subscribe (current-buffer)))
-         (erc-querypoll-mode -1))
-     (cl-assert (not erc--decouple-query-and-channel-membership-p))
-     (setq-local erc--querypoll-ring (make-ring 5))
-     (erc-with-all-buffers-of-server erc-server-process nil
-       (unless erc-querypoll-mode
-         (erc-querypoll-mode +1)))))
-  ((when erc--querypoll-timer
-     (cancel-timer erc--querypoll-timer))
-   (if erc--target
-       (when-let (((erc-query-buffer-p))
-                  (ring (erc-with-server-buffer erc--querypoll-ring))
-                  (index (ring-member ring (current-buffer)))
-                  ((not (erc--querypoll-target-in-chan-p (current-buffer)))))
-         (ring-remove ring index)
-         (unless (erc-current-nick-p (erc-target))
-           (erc-remove-current-channel-member (erc-target))))
-     (erc-with-all-buffers-of-server erc-server-process #'erc-query-buffer-p
-       (erc-querypoll-mode -1)))
-   (kill-local-variable 'erc--querypoll-ring)
-   (kill-local-variable 'erc--querypoll-timer))
-  'local)
-
-(cl-defmethod erc--queries-current-p (&context (erc-querypoll-mode (eql t))) t)
-
-(defvar erc-querypoll-period-params '(10 10 1)
-  "Parameters affecting the delay with respect to the number of buffers.
-The elements represent some parameters of an exponential decay function,
-a(e)^{-x/b}+c.  The first number (a) affects the overall scaling.  A
-higher value means longer delays for all query buffers relative to queue
-length.  The second number (b) determines how quickly the delay
-decreases as the queue length increases.  Larger values make the delay
-taper off more gradually.  The last number (c) sets the minimum delay
-between updates regardless of queue length.")
-
-(defun erc--querypoll-compute-period (queue-size)
-  "Calculate delay based on QUEUE-SIZE."
-  (let ((scale (nth 0 erc-querypoll-period-params))
-        (rate (* 1.0 (nth 1 erc-querypoll-period-params)))
-        (min (nth 2 erc-querypoll-period-params)))
-    (+ (* scale (exp (/ (- queue-size) rate))) min)))
-
-(defun erc--querypoll-target-in-chan-p (buffer)
-  "Determine whether buffer's target, as a user, is joined to any channels."
-  (and-let*
-      ((target (erc--target-string (buffer-local-value 'erc--target buffer)))
-       (user (erc-get-server-user target))
-       (buffers (erc-server-user-buffers user))
-       ((seq-some #'erc-channel-p buffers)))))
-
-(defun erc--querypoll-get-length (ring)
-  "Return the effective length of RING, discounting chan members."
-  (let ((count 0))
-    (dotimes (i (ring-length ring))
-      (unless (erc--querypoll-target-in-chan-p (ring-ref ring i))
-        (cl-incf count 1)))
-    count))
-
-(defun erc--querypoll-get-next (ring)
-  (let ((n (ring-length ring)))
-    (catch 'found
-      (while (natnump (cl-decf n))
-        (when-let ((buffer (ring-remove ring))
-                   ((buffer-live-p buffer)))
-          ;; Push back buffers for users joined to some chan.
-          (if (erc--querypoll-target-in-chan-p buffer)
-              (ring-insert ring buffer)
-            (throw 'found buffer)))))))
-
-(defun erc--querypoll-subscribe (query-buffer &optional penalty)
-  "Add QUERY-BUFFER to FIFO and ensure timer is running."
-  (when query-buffer
-    (cl-assert (erc-query-buffer-p query-buffer)))
-  (erc-with-server-buffer
-    (when (and query-buffer
-               (not (with-current-buffer query-buffer
-                      (or (erc-current-nick-p (erc-target))
-                          (string-match erc-querypoll-exclude-regexp
-                                        (erc-target)))))
-               (not (ring-member erc--querypoll-ring query-buffer)))
-      (ring-insert+extend erc--querypoll-ring query-buffer))
-    (unless erc--querypoll-timer
-      (setq erc--querypoll-timer
-            (let* ((length (erc--querypoll-get-length erc--querypoll-ring))
-                   (period (erc--querypoll-compute-period length)))
-              (run-at-time (+ (or penalty 0) period)
-                           nil #'erc--querypoll-send (current-buffer)))))))
-
-(defun erc--querypoll-on-352 (target-nick args)
-  "Add or update `erc-server-users' data for TARGET-NICK from ARGS.
-Then add user to participant rolls in any existing query buffers."
-  (pcase-let
-      ((`(,_ ,channel ,login ,host ,_server ,nick ,_flags, hop-real) args))
-    (when (and (string= channel "*") (erc-nick-equal-p nick target-nick))
-      (if-let ((user (erc-get-server-user nick)))
-          (erc-update-user user nick host login
-                           (erc--extract-352-full-name hop-real))
-        ;; Don't add unless target is already known.
-        (when (erc-get-buffer nick erc-server-process)
-          (erc-add-server-user
-           nick (make-erc-server-user
-                 :nickname nick :login login :host host
-                 :full-name (erc--extract-352-full-name hop-real)))))
-      (erc--ensure-query-member nick)
-      t)))
-
-;; This uses heuristics to associate replies to the initial request
-;; because ERC does not yet support `labeled-response'.
-(defun erc--querypoll-send (server-buffer)
-  "Send a captive \"WHO\" in SERVER-BUFFER."
-  (when (and (buffer-live-p server-buffer)
-             (buffer-local-value 'erc-server-connected server-buffer))
-    (with-current-buffer server-buffer
-      (setq erc--querypoll-timer nil)
-      (if-let ((buffer (erc--querypoll-get-next erc--querypoll-ring)))
-          (letrec
-              ((target (erc--target-string
-                        (buffer-local-value 'erc--target buffer)))
-               (penalty 0)
-               (here-fn (erc-once-with-server-event
-                         "352" (lambda (_ parsed)
-                                 (erc--querypoll-on-352
-                                  target (erc-response.command-args parsed)))))
-               (done-fn (erc-once-with-server-event
-                         "315"
-                         (lambda (_ parsed)
-                           (if (memq here-fn erc-server-352-functions)
-                               (erc-remove-user
-                                (nth 1 (erc-response.command-args parsed)))
-                             (remove-hook 'erc-server-352-functions here-fn t))
-                           (remove-hook 'erc-server-263-functions fail-fn t)
-                           (remove-hook 'erc-server-315-functions done-fn t)
-                           (erc--querypoll-subscribe buffer penalty)
-                           t)))
-               (fail-fn (erc-once-with-server-event
-                         "263"
-                         (lambda (proc parsed)
-                           (setq penalty 60)
-                           (funcall done-fn proc parsed)
-                           t))))
-            (erc-server-send (concat "WHO " target)))
-        (unless (ring-empty-p erc--querypoll-ring)
-          (erc--querypoll-subscribe nil 30))))))
-
 (provide 'erc-goodies)
 
 ;;; erc-goodies.el ends here
index 45b0fb12c43f291a02741c5138babe4250a9dadb..a32c8b46118e7e2c2f7bcf2b760da6a329df77c2 100644 (file)
@@ -262,6 +262,202 @@ with args, toggle notify status of people."
   (notify-on . "Detected %n on IRC network %m")
   (notify-off . "%n has left IRC network %m"))
 
+
+;;;; Module `querypoll'
+
+;; This module is similar to `notify' in that it periodically tries to
+;; discover whether certain users are online.  Unlike that module, it's
+;; not really configurable.  Rather, it only selects users you've
+;; corresponded with in a query buffer, and it keeps `erc-server-users'
+;; entries for them updated.
+
+(declare-function ring-empty-p "ring" (ring))
+(declare-function ring-insert "ring" (ring item))
+(declare-function ring-insert+extend "ring" (ring item))
+(declare-function ring-length "ring" (ring))
+(declare-function ring-member "ring" (ring item))
+(declare-function ring-ref "ring" (ring index))
+(declare-function ring-remove "ring" (ring &optional index))
+
+(defvar-local erc--querypoll-ring nil)
+(defvar-local erc--querypoll-timer nil)
+
+(defcustom erc-querypoll-exclude-regexp
+  (rx bot (or (: "*" (+ nonl)) (: (+ (in "A-Za-z")) "Serv")) eot)
+  "Pattern to skip polling for bots and services you regularly query."
+  :group 'erc
+  :package-version '(ERC . "5.6")
+  :type 'regexp)
+
+;;;###autoload(autoload 'erc-querypoll-mode "erc-notify" nil t)
+(define-erc-module querypoll nil
+  "Send periodic \"WHO\" requests for each query buffer.
+Omit query participants who are currently present in some channel.
+Instead of announcing arrivals and departures, rely on other modules,
+like `nickbar', to provide UI feedback when changes occur.
+
+Once ERC implements the `monitor' extension, this module will serve as
+an optional fallback for keeping query-participant rolls up to date on
+servers that lack support or are stingy with their allotments.  Until
+such time, this module should be considered experimental.
+
+This is a local ERC module, so selectively polling only a subset of
+query targets is possible but cumbersome.  To do so, ensure
+`erc-querypoll-mode' is enabled in the server buffer, and then toggle it
+as appropriate in desired query buffers.  To stop polling for the
+current connection, toggle off the command \\[erc-querypoll-mode] from a
+server buffer, or run \\`M-x C-u erc-querypoll-disable RET' from a
+target buffer."
+  ((if erc--target
+       (if (erc-query-buffer-p)
+           (progn ; accommodate those who eschew `erc-modules'
+             (erc-with-server-buffer
+               (unless erc-querypoll-mode
+                 (erc-querypoll-mode +1)))
+             (erc--querypoll-subscribe (current-buffer)))
+         (erc-querypoll-mode -1))
+     (cl-assert (not erc--decouple-query-and-channel-membership-p))
+     (setq-local erc--querypoll-ring (make-ring 5))
+     (erc-with-all-buffers-of-server erc-server-process nil
+       (unless erc-querypoll-mode
+         (erc-querypoll-mode +1)))))
+  ((when erc--querypoll-timer
+     (cancel-timer erc--querypoll-timer))
+   (if erc--target
+       (when-let (((erc-query-buffer-p))
+                  (ring (erc-with-server-buffer erc--querypoll-ring))
+                  (index (ring-member ring (current-buffer)))
+                  ((not (erc--querypoll-target-in-chan-p (current-buffer)))))
+         (ring-remove ring index)
+         (unless (erc-current-nick-p (erc-target))
+           (erc-remove-current-channel-member (erc-target))))
+     (erc-with-all-buffers-of-server erc-server-process #'erc-query-buffer-p
+       (erc-querypoll-mode -1)))
+   (kill-local-variable 'erc--querypoll-ring)
+   (kill-local-variable 'erc--querypoll-timer))
+  'local)
+
+(cl-defmethod erc--queries-current-p (&context (erc-querypoll-mode (eql t))) t)
+
+(defvar erc-querypoll-period-params '(10 10 1)
+  "Parameters affecting the delay with respect to the number of buffers.
+The elements represent some parameters of an exponential decay function,
+a(e)^{-x/b}+c.  The first number (a) affects the overall scaling.  A
+higher value means longer delays for all query buffers relative to queue
+length.  The second number (b) determines how quickly the delay
+decreases as the queue length increases.  Larger values make the delay
+taper off more gradually.  The last number (c) sets the minimum delay
+between updates regardless of queue length.")
+
+(defun erc--querypoll-compute-period (queue-size)
+  "Calculate delay based on QUEUE-SIZE."
+  (let ((scale (nth 0 erc-querypoll-period-params))
+        (rate (* 1.0 (nth 1 erc-querypoll-period-params)))
+        (min (nth 2 erc-querypoll-period-params)))
+    (+ (* scale (exp (/ (- queue-size) rate))) min)))
+
+(defun erc--querypoll-target-in-chan-p (buffer)
+  "Determine whether buffer's target, as a user, is joined to any channels."
+  (and-let*
+      ((target (erc--target-string (buffer-local-value 'erc--target buffer)))
+       (user (erc-get-server-user target))
+       (buffers (erc-server-user-buffers user))
+       ((seq-some #'erc-channel-p buffers)))))
+
+(defun erc--querypoll-get-length (ring)
+  "Return the effective length of RING, discounting chan members."
+  (let ((count 0))
+    (dotimes (i (ring-length ring))
+      (unless (erc--querypoll-target-in-chan-p (ring-ref ring i))
+        (cl-incf count 1)))
+    count))
+
+(defun erc--querypoll-get-next (ring)
+  (let ((n (ring-length ring)))
+    (catch 'found
+      (while (natnump (cl-decf n))
+        (when-let ((buffer (ring-remove ring))
+                   ((buffer-live-p buffer)))
+          ;; Push back buffers for users joined to some chan.
+          (if (erc--querypoll-target-in-chan-p buffer)
+              (ring-insert ring buffer)
+            (throw 'found buffer)))))))
+
+(defun erc--querypoll-subscribe (query-buffer &optional penalty)
+  "Add QUERY-BUFFER to FIFO and ensure timer is running."
+  (when query-buffer
+    (cl-assert (erc-query-buffer-p query-buffer)))
+  (erc-with-server-buffer
+    (when (and query-buffer
+               (not (with-current-buffer query-buffer
+                      (or (erc-current-nick-p (erc-target))
+                          (string-match erc-querypoll-exclude-regexp
+                                        (erc-target)))))
+               (not (ring-member erc--querypoll-ring query-buffer)))
+      (ring-insert+extend erc--querypoll-ring query-buffer))
+    (unless erc--querypoll-timer
+      (setq erc--querypoll-timer
+            (let* ((length (erc--querypoll-get-length erc--querypoll-ring))
+                   (period (erc--querypoll-compute-period length)))
+              (run-at-time (+ (or penalty 0) period)
+                           nil #'erc--querypoll-send (current-buffer)))))))
+
+(defun erc--querypoll-on-352 (target-nick args)
+  "Add or update `erc-server-users' data for TARGET-NICK from ARGS.
+Then add user to participant rolls in any existing query buffers."
+  (pcase-let
+      ((`(,_ ,channel ,login ,host ,_server ,nick ,_flags, hop-real) args))
+    (when (and (string= channel "*") (erc-nick-equal-p nick target-nick))
+      (if-let ((user (erc-get-server-user nick)))
+          (erc-update-user user nick host login
+                           (erc--extract-352-full-name hop-real))
+        ;; Don't add unless target is already known.
+        (when (erc-get-buffer nick erc-server-process)
+          (erc-add-server-user
+           nick (make-erc-server-user
+                 :nickname nick :login login :host host
+                 :full-name (erc--extract-352-full-name hop-real)))))
+      (erc--ensure-query-member nick)
+      t)))
+
+;; This uses heuristics to associate replies to the initial request
+;; because ERC does not yet support `labeled-response'.
+(defun erc--querypoll-send (server-buffer)
+  "Send a captive \"WHO\" in SERVER-BUFFER."
+  (when (and (buffer-live-p server-buffer)
+             (buffer-local-value 'erc-server-connected server-buffer))
+    (with-current-buffer server-buffer
+      (setq erc--querypoll-timer nil)
+      (if-let ((buffer (erc--querypoll-get-next erc--querypoll-ring)))
+          (letrec
+              ((target (erc--target-string
+                        (buffer-local-value 'erc--target buffer)))
+               (penalty 0)
+               (here-fn (erc-once-with-server-event
+                         "352" (lambda (_ parsed)
+                                 (erc--querypoll-on-352
+                                  target (erc-response.command-args parsed)))))
+               (done-fn (erc-once-with-server-event
+                         "315"
+                         (lambda (_ parsed)
+                           (if (memq here-fn erc-server-352-functions)
+                               (erc-remove-user
+                                (nth 1 (erc-response.command-args parsed)))
+                             (remove-hook 'erc-server-352-functions here-fn t))
+                           (remove-hook 'erc-server-263-functions fail-fn t)
+                           (remove-hook 'erc-server-315-functions done-fn t)
+                           (erc--querypoll-subscribe buffer penalty)
+                           t)))
+               (fail-fn (erc-once-with-server-event
+                         "263"
+                         (lambda (proc parsed)
+                           (setq penalty 60)
+                           (funcall done-fn proc parsed)
+                           t))))
+            (erc-server-send (concat "WHO " target)))
+        (unless (ring-empty-p erc--querypoll-ring)
+          (erc--querypoll-subscribe nil 30))))))
+
 (provide 'erc-notify)
 
 ;;; erc-notify.el ends here
index 1d74025c5ce8764f6342040118020a543bf43ec8..9a805fb05a65b91d9cb00e1f7404cea20ff1bdcc 100644 (file)
      (should (equal '(3 . 11) (erc--get-inserted-msg-bounds arg))))))
 
 
-;;;; querypoll
-
-(ert-deftest erc--querypoll-compute-period ()
-  (should (equal (mapcar (lambda (i)
-                           (/ (round (* 100 (erc--querypoll-compute-period i)))
-                              100.0))
-                         (number-sequence 0 10))
-                 '(11.0 10.05 9.19 8.41 7.7 7.07 6.49 5.97 5.49 5.07 4.68))))
-
-(declare-function ring-insert "ring" (ring item))
-
-(ert-deftest erc--querypoll-target-in-chan-p ()
-  (erc-tests-common-make-server-buf)
-  (with-current-buffer (erc--open-target "#chan")
-    (erc-update-current-channel-member "bob" "bob" 'addp))
-
-  (with-current-buffer (erc--open-target "bob")
-    (should (erc--querypoll-target-in-chan-p (current-buffer))))
-
-  (with-current-buffer (erc--open-target "alice")
-    (should-not (erc--querypoll-target-in-chan-p (current-buffer))))
-
-  (when noninteractive
-    (erc-tests-common-kill-buffers)))
-
-(ert-deftest erc--querypoll-get-length ()
-  (erc-tests-common-make-server-buf)
-  (with-current-buffer (erc--open-target "#chan")
-    (erc-update-current-channel-member "bob" "bob" 'addp))
-
-  (let ((ring (make-ring 5)))
-    (ring-insert ring (with-current-buffer (erc--open-target "bob")))
-    (should (= 0 (erc--querypoll-get-length ring)))
-    (ring-insert ring (with-current-buffer (erc--open-target "alice")))
-    (should (= 1 (erc--querypoll-get-length ring))))
-
-  (when noninteractive
-    (erc-tests-common-kill-buffers)))
-
-(ert-deftest erc--querypoll-get-next ()
-  (erc-tests-common-make-server-buf)
-  (with-current-buffer (erc--open-target "#chan")
-    (erc-update-current-channel-member "bob" "bob" 'addp)
-    (erc-update-current-channel-member "alice" "alice" 'addp))
-
-  (let ((ring (make-ring 5)))
-    (ring-insert ring (with-current-buffer (erc--open-target "bob")))
-    (ring-insert ring (with-current-buffer (erc--open-target "dummy")))
-    (ring-insert ring (with-current-buffer (erc--open-target "alice")))
-    (ring-insert ring (with-current-buffer (erc--open-target "tester")))
-    (kill-buffer (get-buffer "dummy"))
-
-    (should (eq (get-buffer "tester") (erc--querypoll-get-next ring))))
-
-  (when noninteractive
-    (erc-tests-common-kill-buffers)))
-
 ;;; erc-goodies-tests.el ends here
diff --git a/test/lisp/erc/erc-notify-tests.el b/test/lisp/erc/erc-notify-tests.el
new file mode 100644 (file)
index 0000000..f8dafb0
--- /dev/null
@@ -0,0 +1,87 @@
+;;; erc-notify-tests.el --- Tests for erc-notify  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation, either version 3 of the License,
+;; or (at your option) any later version.
+;;
+;; GNU Emacs is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;; Code:
+(require 'erc-notify)
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-tests-common)))
+
+
+;;;; Module `querypoll'
+
+(ert-deftest erc--querypoll-compute-period ()
+  (should (equal (mapcar (lambda (i)
+                           (/ (round (* 100 (erc--querypoll-compute-period i)))
+                              100.0))
+                         (number-sequence 0 10))
+                 '(11.0 10.05 9.19 8.41 7.7 7.07 6.49 5.97 5.49 5.07 4.68))))
+
+(declare-function ring-insert "ring" (ring item))
+
+(ert-deftest erc--querypoll-target-in-chan-p ()
+  (erc-tests-common-make-server-buf)
+  (with-current-buffer (erc--open-target "#chan")
+    (erc-update-current-channel-member "bob" "bob" 'addp))
+
+  (with-current-buffer (erc--open-target "bob")
+    (should (erc--querypoll-target-in-chan-p (current-buffer))))
+
+  (with-current-buffer (erc--open-target "alice")
+    (should-not (erc--querypoll-target-in-chan-p (current-buffer))))
+
+  (when noninteractive
+    (erc-tests-common-kill-buffers)))
+
+(ert-deftest erc--querypoll-get-length ()
+  (erc-tests-common-make-server-buf)
+  (with-current-buffer (erc--open-target "#chan")
+    (erc-update-current-channel-member "bob" "bob" 'addp))
+
+  (let ((ring (make-ring 5)))
+    (ring-insert ring (with-current-buffer (erc--open-target "bob")))
+    (should (= 0 (erc--querypoll-get-length ring)))
+    (ring-insert ring (with-current-buffer (erc--open-target "alice")))
+    (should (= 1 (erc--querypoll-get-length ring))))
+
+  (when noninteractive
+    (erc-tests-common-kill-buffers)))
+
+(ert-deftest erc--querypoll-get-next ()
+  (erc-tests-common-make-server-buf)
+  (with-current-buffer (erc--open-target "#chan")
+    (erc-update-current-channel-member "bob" "bob" 'addp)
+    (erc-update-current-channel-member "alice" "alice" 'addp))
+
+  (let ((ring (make-ring 5)))
+    (ring-insert ring (with-current-buffer (erc--open-target "bob")))
+    (ring-insert ring (with-current-buffer (erc--open-target "dummy")))
+    (ring-insert ring (with-current-buffer (erc--open-target "alice")))
+    (ring-insert ring (with-current-buffer (erc--open-target "tester")))
+    (kill-buffer (get-buffer "dummy"))
+
+    (should (eq (get-buffer "tester") (erc--querypoll-get-next ring))))
+
+  (when noninteractive
+    (erc-tests-common-kill-buffers)))
+
+;;; erc-notify-tests.el ends here