emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

fix/bug-48598 c2943fec8e 1/2: [CATCH-UP] Synchronize with patch set


From: F. Jason Park
Subject: fix/bug-48598 c2943fec8e 1/2: [CATCH-UP] Synchronize with patch set
Date: Wed, 4 May 2022 02:30:46 -0400 (EDT)

branch: fix/bug-48598
commit c2943fec8eac4039a7069cacbd4224f91adae0ed
Author: F. Jason Park <jp@neverwas.me>
Commit: F. Jason Park <jp@neverwas.me>

    [CATCH-UP] Synchronize with patch set
    
    Note: this branch exists for testing purposes and not for code sharing
    or merging.  The bug fix continues to be developed as a series of
    discrete patches (see email thread for download link).  Commits like
    this apply recent changes from the bug set, basically an "--interdiff"
    or "--range-diff" from git-format-patch(1).  Non-ERC parts of the tree
    are behind master.
    
    This also includes the following from master, but only the hunks that
    touch lisp/erc, test/lisp/erc, doc/misc/erc.texi, or etc/ERC-NEWS:
    
    * 2d71fd3b04 Further doc string quoting fixes
    * bbf389ea6d Audit quoting the quote character in doc strings
---
 doc/misc/erc.texi                                  |  151 +-
 lisp/erc/erc-backend.el                            |   16 +-
 lisp/erc/erc-compat.el                             |   86 -
 lisp/erc/erc-join.el                               |    6 +-
 lisp/erc/erc-networks.el                           |   21 +-
 lisp/erc/erc-services.el                           |   19 +-
 lisp/erc/erc.el                                    |  235 +-
 .../erc-d/erc-d-self-resources/proxy-subprocess.el |   26 -
 test/lisp/erc/erc-join-tests.el                    |   40 +-
 test/lisp/erc/erc-scenarios-common.el              |  152 --
 test/lisp/erc/erc-scenarios.el                     | 2312 --------------------
 .../erc/{erc-d => erc-scenarios}/erc-d-self.el     |   89 +-
 .../erc/erc-scenarios/erc-scenarios-auth-source.el |  178 ++
 .../erc-scenarios-base-association-nick.el         |  164 ++
 .../erc-scenarios-base-association-samenet.el      |  144 ++
 .../erc-scenarios-base-association.el              |  196 ++
 .../erc-scenarios-base-compat-rename-bouncer.el    |  175 ++
 .../erc-scenarios-base-misc-regressions.el         |  135 ++
 .../erc-scenarios-base-netid-bouncer-id.el         |   34 +
 .../erc-scenarios-base-netid-bouncer-recon-base.el |   30 +
 .../erc-scenarios-base-netid-bouncer-recon-both.el |   32 +
 .../erc-scenarios-base-netid-bouncer-recon-id.el   |   35 +
 .../erc-scenarios-base-netid-bouncer.el            |   35 +
 .../erc-scenarios-base-netid-samenet.el            |  147 ++
 .../erc-scenarios/erc-scenarios-base-reconnect.el  |  227 ++
 .../erc/erc-scenarios/erc-scenarios-base-renick.el |  310 +++
 .../erc-scenarios-base-reuse-buffers.el            |  189 ++
 .../erc-scenarios/erc-scenarios-base-unstable.el   |  137 ++
 .../erc-scenarios-join-netid-newcmd-id.el          |   50 +
 .../erc-scenarios-join-netid-newcmd.el             |   37 +
 .../erc-scenarios-join-netid-recon-id.el           |   46 +
 .../erc-scenarios-join-netid-recon.el              |   36 +
 test/lisp/erc/erc-scenarios/erc-scenarios-misc.el  |  145 ++
 .../erc-scenarios/erc-scenarios-services-misc.el   |   86 +
 .../base/association/bouncer-history/barnet.eld    |    2 +-
 .../base/association/bouncer-history/foonet.eld    |    0
 .../base/association/multi-net/barnet.eld          |    2 +-
 .../base/association/multi-net/foonet.eld          |    2 +-
 .../base/association/nick-bump/renicked-again.eld  |    4 +-
 .../nick-bump/renicked-foisted-again.eld           |    0
 .../association/nick-bump/renicked-foisted.eld     |    0
 .../base/association/nick-bump/renicked.eld        |    2 +-
 .../reconnect-playback/foonet-again.eld            |    4 +-
 .../base/association/reconnect-playback/foonet.eld |    6 +-
 .../base/association/same-network/chester.eld      |    2 +-
 .../base/association/same-network/tester-again.eld |    2 +-
 .../base/association/same-network/tester.eld       |    2 +-
 .../resources}/base/auth-source/foonet.eld         |    0
 .../resources}/base/auth-source/nopass.eld         |    0
 .../base/channel-buffer-revival/foonet.eld         |    2 +-
 .../resources}/base/flood/soju.eld                 |    0
 .../resources}/base/gapless-connect/barnet.eld     |    2 +-
 .../resources}/base/gapless-connect/foonet.eld     |    2 +-
 .../resources}/base/gapless-connect/pass-stub.eld  |    0
 .../resources}/base/mask-target-routing/foonet.eld |    0
 .../base/network-id/bouncer/barnet-again.eld       |   10 +-
 .../base/network-id/bouncer/barnet-drop.eld        |    2 +-
 .../resources}/base/network-id/bouncer/barnet.eld  |   10 +-
 .../base/network-id/bouncer/foonet-again.eld       |    8 +-
 .../base/network-id/bouncer/foonet-drop.eld        |    2 +-
 .../resources}/base/network-id/bouncer/foonet.eld  |   10 +-
 .../base/network-id/bouncer/stub-again.eld         |    0
 .../base/network-id/same-network/chester.eld       |    2 +-
 .../base/network-id/same-network/tester.eld        |    2 +-
 .../resources}/base/reconnect/aborted-dupe.eld     |    0
 .../resources}/base/reconnect/aborted.eld          |    0
 .../resources}/base/reconnect/options-again.eld    |    0
 .../resources}/base/reconnect/options.eld          |    0
 .../resources}/base/reconnect/timer-last.eld       |    0
 .../resources}/base/reconnect/timer.eld            |    0
 .../base/renick/queries/bouncer-barnet.eld         |   10 +-
 .../base/renick/queries/bouncer-foonet.eld         |    4 +-
 .../resources}/base/renick/queries/solo.eld        |    2 +-
 .../resources}/base/renick/self/auto.eld           |    4 +-
 .../resources}/base/renick/self/manual.eld         |    0
 .../resources}/base/renick/self/qual-chester.eld   |    0
 .../resources}/base/renick/self/qual-tester.eld    |    0
 .../base/reuse-buffers/channel-buffers/barnet.eld  |   10 +-
 .../base/reuse-buffers/channel-buffers/foonet.eld  |    4 +-
 .../base/reuse-buffers/server-buffers/barnet.eld   |    2 +-
 .../base/reuse-buffers/server-buffers/foonet.eld   |    2 +-
 .../{ => erc-scenarios/resources}/erc-d/erc-d-i.el |    0
 .../{ => erc-scenarios/resources}/erc-d/erc-d-t.el |    9 +-
 .../{ => erc-scenarios/resources}/erc-d/erc-d-u.el |    2 +-
 .../{ => erc-scenarios/resources}/erc-d/erc-d.el   |   33 +-
 .../resources/erc-d/resources}/basic.eld           |    0
 .../resources/erc-d/resources}/depleted.eld        |    0
 .../resources/erc-d/resources}/drop-a.eld          |    0
 .../resources/erc-d/resources}/drop-b.eld          |    0
 .../resources/erc-d/resources}/dynamic-barnet.eld  |    0
 .../resources/erc-d/resources}/dynamic-foonet.eld  |    0
 .../resources/erc-d/resources}/dynamic-stub.eld    |    0
 .../resources/erc-d/resources}/dynamic.eld         |    8 +-
 .../resources/erc-d/resources}/eof.eld             |    0
 .../resources/erc-d/resources}/fuzzy.eld           |    0
 .../resources/erc-d/resources}/incremental.eld     |    0
 .../erc-d/resources}/irc-parser-tests.eld          |    0
 .../resources/erc-d/resources}/linger-multi-a.eld  |    0
 .../resources/erc-d/resources}/linger-multi-b.eld  |    0
 .../resources/erc-d/resources}/linger.eld          |    0
 .../resources/erc-d/resources}/no-block.eld        |    2 +-
 .../resources/erc-d/resources}/no-match.eld        |    0
 .../resources/erc-d/resources}/no-pong.eld         |    0
 .../resources/erc-d/resources}/nonstandard.eld     |    0
 .../resources/erc-d/resources}/proxy-barnet.eld    |    0
 .../resources/erc-d/resources}/proxy-foonet.eld    |    0
 .../resources/erc-d/resources}/proxy-solo.eld      |    0
 .../resources/erc-d/resources/proxy-subprocess.el  |   45 +
 .../resources/erc-d/resources}/timeout.eld         |    0
 .../resources/erc-d/resources}/unexpected.eld      |    0
 .../resources/erc-scenarios-common.el              |  467 ++++
 .../resources}/join/legacy/foonet.eld              |    0
 .../resources}/join/network-id/barnet.eld          |    2 +-
 .../resources}/join/network-id/foonet-again.eld    |    4 +-
 .../resources}/join/network-id/foonet.eld          |    8 +-
 .../resources}/join/reconnect/foonet-again.eld     |    0
 .../resources}/join/reconnect/foonet.eld           |    0
 .../networks/announced-missing/foonet.eld          |    0
 .../resources}/services/auth-source/libera.eld     |    0
 .../resources}/services/password/libera.eld        |    0
 test/lisp/erc/erc-services-tests.el                |  634 ++++--
 test/lisp/erc/erc-tests.el                         |   59 +-
 122 files changed, 4005 insertions(+), 3107 deletions(-)

diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi
index b96e3d886d..22e27c411e 100644
--- a/doc/misc/erc.texi
+++ b/doc/misc/erc.texi
@@ -752,9 +752,9 @@ string abiding by the rules of the network.
 @cindex password
 
 @defopt erc-prompt-for-password
-If non-@code{nil} (the default), @kbd{M-x erc} prompts for a server
-password.  This only affects interactive invocations of @code{erc} and
-@code{erc-tls}.
+If non-@var{nil} (the default), @kbd{M-x erc @key{RET}} prompts for a
+server password.  This only affects interactive invocations of
+@command{erc} and @command{erc-tls}.
 @end defopt
 
 @noindent
@@ -769,32 +769,29 @@ machine irc.example.net login mynick password sEcReT
 
 @noindent
 Here, @code{irc.example.net} corresponds to the @var{server} param
-passed to @code{erc} or @code{erc-tls} or provideed at the ``IRC
+passed to @command{erc} or @command{erc-tls} or provideed at the ``IRC
 server:'' prompt.  Unfortunately, specifying a network, like
-``Libera.Chat'', or a specific network server, like
-@code{zirconium.libera.chat}, won't work for this introductory
-exchange because IRC servers don't provide such information up front.
+``Libera.Chat,'' or a specific network server, like
+``zirconium.libera.chat,'' won't work for this introductory exchange
+because IRC servers don't provide such information up front.
 
 If ERC can't find a suitable server password, it'll just skip the IRC
-@code{PASS} command altogether, something users may want when using
+``PASS'' command altogether, something users may want when using
 CertFP or engaging NickServ via ERC's ``services'' module.  If that
-sounds like you, set the option @code{erc-connect-auth-source-host} to
-@code{nil}.  You can also set it to @code{t} to tell ERC to favor a
-``network identifier'' (corresponding to the @var{id} parameter of
-@code{erc-tls}) as the ``machine'' field and to fall back on
-@var{server} when an @var{id} hasn't been provided.  Note that some
-networks and IRCds may support account-services authentication via
-server password when specified using the non-standard
-``mynick:sEcReT'' convention.
+sounds like you, set the option
+@code{erc-auth-source-parameters-server-function} to @code{nil}.  Note
+that some networks and IRCds may support account-services
+authentication via server password when specified using the
+non-standard ``mynick:sEcReT'' convention.
 
 @code{auth-source} can also be used to authenticate to account
 services the traditional way, through a bot called ``NickServ''.  To
 tell ERC to do that, set the option
 @code{erc-use-auth-source-for-nickserv-password} to @code{t}.  For
 these queries, entries featuring user-provided IDs and networks are
-matched first, followed by network-specific servers and dialed TCP
-endpoints (the @var{SERVER} passed to @code{erc}). The following
-netrc-style entries appear in order of precedence:
+matched first, followed by network-specific servers and dialed
+endpoints (typically, the @var{SERVER} passed to @command{erc}). The
+following netrc-style entries appear in order of precedence:
 
 @example
 machine Libera/cellphone login "mynick" password sEcReT
@@ -803,21 +800,74 @@ machine zirconium.libera.chat login "mynick" password 
sEcReT
 machine irc.libera.chat login "mynick" password sEcReT
 @end example
 
-@noindent
-To modify this behavior, for example by signaling an error instead of
-degrading gracefully (which may feel like anything but, depending on
-your use case), see option @code{erc-auth-source-parameters-function}.
+If this fallback stuff doesn't appeal to you, see the option
+@code{erc-auth-source-parameters-server-function} and friends just
+below.  These let you decide based on context how a connection's
+parameters should map to auth-source queries.  But most users can just
+ignore all the details and get by with something like the following as
+values:
+
+@example
+(defun my-erc-auth-source-server (&rest _)
+  (list :host "MyHost"))
+
+;; or
+
+(defun my-erc-auth-source-services (&rest _)
+  (list :host (read-string "Auth-source host: ")))
+@end example
+
+ERC also consults @code{auth-source} to find ``keys'' that may be
+required by certain channels you join.  When modifying a traditional
+@code{auth-source} entry for such use, put the channel name, for
+example ``##my-plus-k-chan,'' in the value of the ``user'' field (also
+known as the ``login'' field). The actual key goes in the ``password''
+(or ``secrets'') field.
+
+For details, @pxref{Top,,auth-source, auth, Emacs auth-source Library}.
+
+@defopt erc-auth-source-parameters-server-function
+@end defopt
+@defopt erc-auth-source-parameters-services-function
+@end defopt
+@defopt erc-auth-source-parameters-join-function
 
-ERC also consults @code{auth-source} to find any channel keys required
-for the channels that you wish to autojoin, as specified by the
-variable @code{erc-autojoin-channels-alist}.  When modifying a
-netrc-style @code{auth-source} file for such use, ensure an entry
-exists with the channel name, for example ``#erc'', in the value of
-the ``login'' field (also known as the ``user'' field). The actual key
-goes in the ``password'' field.
+ERC calls these functions with keyword arguments recognized by
+`auth-source-search', namely, those deemed most relevant to the
+current context.  For example, with NickServ queries, @code{:user}
+will be the ``desired'' nickname rather than the current one.
+Generalized names, like @code{:user} and @code{:host}, are always used
+over back-end specific ones, like @code{:login} or @code{:machine}.
+ERC expects a refined plist in return.
 
-For more details, @pxref{Top,,auth-source, auth, Emacs auth-source Library}.
+The ordering of the returned pairs influences how results are filtered
+as does the ordering of the members of any individual composite
+values.  If necessary, the former (pairs) takes priority over the
+latter (values).  For example, if the function returns
 
+@example
+(:host (foo bar) :port (123 456))
+@end example
+
+the secret from an auth-source entry of host foo and port 456 will be
+chosen over another of host bar and port 123.  However, if the
+function returns
+
+@example
+(:port (123 456) :host (foo bar))
+@end example
+
+the opposite will be true.  In both cases, two entries with the same
+host but different ports would see the one with port 123 being
+selected.  Much the same would happen for entries sharing only a port:
+the one with host foo would win.
+
+Some auth-source back ends may not be compatible; netrc, plstore,
+json, and secrets are currently supported.
+
+The default value for all variants is currently
+@code{erc-auth-source-determine-params-merge}.
+@end defopt
 
 @subheading Full name
 
@@ -844,26 +894,25 @@ This can be either a string or a function to call.
 
 
 @subheading ID
-Every IRC connection has a ``network identifier''.  It's an abstract
-concept used internally for referring to a connection, primarily for
-the purpose of wrangling buffers.  An ID is normally derived from a
-combination of logical and physical connection parameters (typically
-IRC and TCP, respectively).  Explicitly providing one to an
-entry-point function (like @code{erc-tls}) is rarely needed except in
-rare situations where ERC would otherwise have trouble discerning
-between connections.
-
-One such situation would be to allow multiple connections to the same
-network with the same nick but different (non-standard) "device"
-identifiers, which some bouncers may support.  Another might be to
-mimic the experience of popular standalone clients, which normally
-offer ``named'' persistent configurations with server buffers
-reflecting those names.  Yet another use case might involve
-third-party code needing to identify a connection unequivocally but in
-a human-friendly way devoid of noisy suffixes.
-
-Strings and symbols make the most sense when providing an ID as an
-entry-point argument, but any printable object is acceptable.
+ERC uses an abstract designation called a ``network identifier'' for
+referring to a connection internally.  While normally derived from a
+combination of logical and physical connection parameters, an ID can
+also be explicitly provided via an entry-point command (like
+@command{erc-tls}). Use this in rare situations where ERC would
+otherwise have trouble discerning between connections.
+
+One such situation might see multiple connections to the same network
+using the same nick but different (non-standard) "device" identifiers,
+which some bouncers may support.  Another might be to mimic the
+experience offered by popular standalone clients, which normally offer
+``named'' persistent configurations with server buffers reflecting
+those names.  Yet another use case might involve third-party code
+needing to identify a connection unequivocally but in a human-friendly
+way suitable for UI components.
+
+When providing an ID as an entry-point argument, strings and symbols
+make the most sense, but any reasonably printable object is
+acceptable.
 
 
 @node Sample Configuration
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 1335ee41fb..34de5d48b9 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -479,7 +479,7 @@ If POS is out of range, the value is nil."
 (defun erc-bounds-of-word-at-point ()
   "Return the bounds of word at point, or nil if we're not at a word.
 If no `subword-mode' is active, then this is
-\(bounds-of-thing-at-point 'word)."
+\(bounds-of-thing-at-point \\='word)."
   (if (or (erc-word-at-arg-p (point))
           (erc-word-at-arg-p (1- (point))))
       (save-excursion
@@ -732,14 +732,20 @@ Conditionally try to reconnect and take appropriate 
action."
   (erc-with-all-buffers-of-server
       proc nil ; sorta wish this was indent 2
       (when (and erc-hide-prompt
-                 (memq erc-hide-prompt
-                       (list t (if (erc-default-target) 'target 'server)))
+                 (or (eq erc-hide-prompt t)
+                     ;; FIXME use `erc--target' after bug#48598
+                     (memq (if (erc-default-target)
+                               (if (erc-channel-p (car erc-default-recipients))
+                                   'channel
+                                 'query)
+                             'server)
+                           erc-hide-prompt))
                  (marker-position erc-insert-marker)
                  (marker-position erc-input-marker)
                  (get-text-property erc-insert-marker 'erc-prompt))
         (with-silent-modifications
-          (add-text-properties erc-insert-marker
-                               erc-input-marker `(display ,erc-prompt-hidden)))
+          (add-text-properties erc-insert-marker (1- erc-input-marker)
+                               `(display ,erc-prompt-hidden)))
         (add-hook 'pre-command-hook #'erc--unhide-prompt-on-self-insert 0 t))))
 
 (defun erc-process-sentinel (cproc event)
diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el
index a833a61456..16cfb15a5a 100644
--- a/lisp/erc/erc-compat.el
+++ b/lisp/erc/erc-compat.el
@@ -150,92 +150,6 @@ If START or END is negative, it counts from the end."
                 (setq i (1+ i) start (1+ start)))
               res))))))
 
-;;;; Auth Source
-
-;; We want a unified interface to auth-source, but that depends on
-;; upstream providing a consistent experience.  As of at least
-;;
-;;   lisp/auth-source-pass.el: Support multiple hosts in search
-;;   b09ee1406205e8b6298411b9a18c1cd26e201689 Fri Jul 2 2021
-;;
-;; auth-source-pass only returns singletons on success.  But we want
-;; all possible matches.  This provides some hacks to do that, but it
-;; depends on internal functions.  We also need to pass lists of
-;; candidates for host, user, and port selectors, which aren't yet
-;; fully supported.
-;;
-
-(require 'auth-source)
-
-(declare-function auth-source-pass--get-attr
-                  "auth-source-pass" (key entry-data))
-(declare-function auth-source-pass--disambiguate
-                  "auth-source-pass" (host &optional user port))
-(declare-function auth-source-pass--find-match-unambiguous
-                  "auth-source-pass" (hostname user port))
-(declare-function auth-source-backend-parse-parameters
-                  "auth-source-pass" (entry backend))
-
-(defun erc-compat--auth-source-pass--couch (s)
-  (lambda () (auth-source-pass--get-attr 'secret s)))
-
-(defun erc-compat--auth-source-pass--find-match (hosts ports users)
-  "Return a plist of HOSTS, PORTS, USERS, and secret.
-This is not a drop-in for `auth-source-pass--find-match', which
-returns an alist."
-  (unless (listp hosts) (setq hosts (list hosts)))
-  (unless (listp users) (setq users (list users)))
-  (unless (listp ports) (setq ports (list ports)))
-  ;; Try combinations of Hosts x Users x Ports, filter out nonexistent
-  (cl-loop for host in hosts
-           for (h u p) = (auth-source-pass--disambiguate host)
-           append
-           (cl-loop for user in (or users (list u))
-                    append
-                    (cl-loop for port in (or ports (list p))
-                             for s = (auth-source-pass--find-match-unambiguous
-                                      h user port)
-                             when s collect
-                             ;; Keep original host
-                             `(:host
-                               ,host
-                               ,@(and user (list :user user))
-                               ,@(and port (list :port port))
-                               :secret
-                               ,(erc-compat--auth-source-pass--couch s))))))
-
-(defun erc-compat--auth-source-pass--build-result (hosts ports users
-                                                         &optional max)
-  "Multi-valued `auth-source-pass--build-result'."
-  (unless max (setq max 1))
-  (let ((entries (erc-compat--auth-source-pass--find-match hosts ports users))
-        (count -1)
-        entry
-        out)
-    (while (and (setq entry (pop entries)) (< (cl-incf count) max))
-      (push entry out))
-    out))
-
-(cl-defun erc-compat--auth-source-pass-search
-    (&rest spec &key backend type host user port max &allow-other-keys)
-  (cl-assert (or (null type) (eq type (oref backend type)))
-             t "Invalid password-store search: %s %s")
-  (cl-assert (and host (not (eq host t)))
-             t "Invalid password-store search: %s %s")
-  (erc-compat--auth-source-pass--build-result host port user max))
-
-;; Temporary until we decide whether to load compat by default
-
-;;;###autoload
-(defun erc-compat--auth-source-pass-backend-parse (entry)
-  (when (eq entry 'password-store)
-    (auth-source-backend-parse-parameters
-     entry (auth-source-backend
-            :source "."
-            :type 'password-store
-            :search-function #'erc-compat--auth-source-pass-search))))
-
-
 (provide 'erc-compat)
 
 ;;; erc-compat.el ends here
diff --git a/lisp/erc/erc-join.el b/lisp/erc/erc-join.el
index b812dfc512..01dceffdde 100644
--- a/lisp/erc/erc-join.el
+++ b/lisp/erc/erc-join.el
@@ -175,8 +175,8 @@ Respects `erc-autojoin-domain-only'."
   "Add the channel being joined to `erc-autojoin-channels-alist'."
   (when-let* ((nick (car (erc-parse-user (erc-response.sender parsed))))
               ((erc-current-nick-p nick))
-              (chnl (erc-response.contents parsed))
-              (elem (or (and (erc-valid-local-channel-p chnl)
+              (chnl (car (erc-response.command-args parsed)))
+              (elem (or (and (erc--valid-local-channel-p chnl)
                              (regexp-quote erc-server-announced-name))
                         (erc-networks--id-symbol erc-networks--id)
                         (with-current-buffer (process-buffer proc)
@@ -195,7 +195,7 @@ Respects `erc-autojoin-domain-only'."
   (when-let* ((nick (car (erc-parse-user (erc-response.sender parsed))))
               ((erc-current-nick-p nick))
               (chnl (car (erc-response.command-args parsed)))
-              (elem (or (and (erc-valid-local-channel-p chnl)
+              (elem (or (and (erc--valid-local-channel-p chnl)
                              (regexp-quote erc-server-announced-name))
                         (erc-networks--id-symbol erc-networks--id)
                         (with-current-buffer (process-buffer proc)
diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el
index 5a97024ea0..0c9c2b41b2 100644
--- a/lisp/erc/erc-networks.el
+++ b/lisp/erc/erc-networks.el
@@ -740,7 +740,7 @@ MATCHER is used to find a corresponding network to a server 
while
 ;; This relationship is quasi-permanent and transcends IRC connections
 ;; and Emacs sessions.  As of early 2022, whether a user is
 ;; authenticated (logged in to an account) remains orthogonal to their
-;; network identity from a client's perspective. ERC must be equipped
+;; network identity from a client's perspective.  ERC must be equipped
 ;; to adapt should this ever change.
 ;;
 ;; While a connection is normally associated with exactly one nick,
@@ -762,7 +762,7 @@ Here, \"presence\" refers to some local state representing 
a persistent
 existence on a network.  The management of this state involves tracking
 associated buffers and what they're displaying.  Since a presence can
 outlast physical connections and survive changes in back-end transports
-\(and even outlive Emacs sessions), its identity must remain resilient.
+\(and even outlive Emacs sessions), its identity must be resilient.
 
 Essential to this notion of an enduring existence on a network is
 ensuring recovery from the loss of a server buffer.  Thus, any useful
@@ -792,18 +792,19 @@ set of connection parameters.  See the constructor
                               (len 1))))
   "A network presence identified by certain connection parameters.
 Two identifiers are considered equivalent when their non-empty `parts'
-slots compare equal.  Identifiers sharing a common prefix of `parts' are
-considered related.  An identifier's canonical ID is determined by
-concatenating the shortest prefix (non-empty initial substring of
-`parts') unique among those of its relatives.  For example, related
-presences [b a r d o] and [b a z a r] would have IDs b/a/r and b/a/z
-respectively.  The separator is given by `erc-networks--id-sep'."
+slots compare equal.  Related identifiers share a common prefix of
+`parts' taken from connection parameters (given or discovered).  An
+identifier's unique `symbol', intended for display purposes, is created
+by concatenating the shortest common prefix among its relatives.  For
+example, related presences [b a r d o] and [b a z a r] would have IDs
+b/a/r and b/a/z respectively.  The separator is given by
+`erc-networks--id-sep'."
   (parts nil :type sequence ; a vector of atoms
          :documentation "Sequence of identifying components.")
   (len 0 :type integer
        :documentation "Length of active `parts' interval."))
 
-;; Please use this instead of `erc-networks--id-fixed-p'.
+;; For now, please use this instead of `erc-networks--id-fixed-p'.
 (cl-defgeneric erc-networks--id-given (net-id)
   "Return the preassigned identifier for a network presence, if any.
 This may have come in the form of an :id arg to an \"entry-point\"
@@ -909,7 +910,7 @@ them with string separator `erc-networks--id-sep'."
          (erc-networks--id-telescopic-parts nid))))
 
 (defun erc-networks--id-telescopic-prefix-length (nid-a nid-b)
-  "Return length of common initial prefix of NID-a and NID-B.
+  "Return length of common initial prefix of NID-A and NID-B.
 Return nil when no such sequence exists (instead of zero)."
   (when-let* ((a (erc-networks--id-telescopic-parts nid-a))
               (b (erc-networks--id-telescopic-parts nid-b))
diff --git a/lisp/erc/erc-services.el b/lisp/erc/erc-services.el
index f042a52250..ca3eca5bdb 100644
--- a/lisp/erc/erc-services.el
+++ b/lisp/erc/erc-services.el
@@ -174,6 +174,18 @@ function `erc-nickserv-get-password'."
   :version "28.1"
   :type 'boolean)
 
+(defcustom erc-auth-source-parameters-services-function
+  #'erc-auth-source-determine-params-merge
+  "NickServ-specific filter for `auth-source-search'.
+Called with keyword parameters known to `auth-source-search' and
+relevant to authenticating to nickname services.  In return, ERC expects
+a possibly modified set of parameters for querying auth-source and for
+narrowing the results.  See info node `(erc) Connecting' for details."
+  :package-version '(ERC . "5.4.1") ; FIXME update when publishing to ELPA
+  :type '(choice (const erc-auth-source-determine-params-merge)
+                 (const nil)
+                 function))
+
 (defcustom erc-nickserv-passwords nil
   "Passwords used when identifying to NickServ automatically.
 `erc-prompt-for-nickserv-password' must be nil for these
@@ -436,8 +448,11 @@ it returns nil."
        (ret (or (when erc-nickserv-passwords
                   (assoc-default nick
                                  (cadr (assq esid erc-nickserv-passwords))))
-                (when erc-use-auth-source-for-nickserv-password
-                  (erc--auth-source-search :user nick))
+                (when (and erc-use-auth-source-for-nickserv-password
+                           erc-auth-source-parameters-services-function)
+                  (apply #'erc--auth-source-search
+                         (funcall erc-auth-source-parameters-services-function
+                                  :user nick)))
                 (when erc-prompt-for-nickserv-password
                   (read-passwd
                    (format "NickServ password for %s on %s (RET to cancel): "
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index f25b2f2305..e8ee7b3710 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -249,28 +249,6 @@ node `(auth) Top' and info node `(erc) Connecting'.")
   :group 'erc
   :type 'boolean)
 
-(defcustom erc-connect-auth-source-host 'server
-  "Host \"type\" for querying auth-source when first connecting.
-This is for determining the \"server password\" argument of the IRC
-\"PASS\" command sent to the server.  The entry points `erc' and
-`erc-tls' query auth-source for such a password when a :password
-argument isn't provided.  Because ERC also interfaces with auth-source
-for other secrets, such as NickServ passwords and channel keys,
-additional ways of selecting entries are sometimes necessary.  See info
-node `(auth) Top'.
-
-Note that there aren't any options for specifying a network, like
-Libera.Chat, or a network-specific server, such as foo.libera.chat,
-because such information isn't available until after initial
-introductions have completed (\"registration\" in IRC speak)."
-  :package-version '(ERC . "5.4.1") ; FIXME increment upon publishing to ELPA
-  :group 'erc
-  :type '(choice (const :tag "Don't query auth-source" nil)
-                 (const :tag "Dialed host name or IP address" server)
-                 (const :tag "Prompt for a machine/host value" prompt)
-                 (const :tag "Session ID, if set, otherwise server" t)
-                 (string :tag "Literal value to use for :host")))
-
 (defcustom erc-warn-about-blank-lines t
   "Warn the user if they attempt to send a blank line."
   :group 'erc
@@ -291,14 +269,25 @@ introductions have completed (\"registration\" in IRC 
speak)."
   "If non-nil, hide input prompt upon disconnecting.
 To unhide, type something in the input area.  Once revealed, a prompt
 remains unhidden until the next disconnection.  Channel prompts are
-unhidden upon rejoining.  Query prompts remain hidden until user input
-is detected or a new message arrives from the target."
+unhidden upon rejoining.  See `erc-unhide-query-prompt' for behavior
+concerning query prompts."
   :package-version '(ERC . "5.4.1") ; FIXME increment on next ELPA release
   :group 'erc-display
   :type '(choice (const :tag "Always hide prompt" t)
-                 (const :tag "Never hide prompt" nil)
-                 (const :tag "Only hide target prompt" 'target)
-                 (const :tag "Only hide server prompt" 'server)))
+                 (set (const server)
+                      (const query)
+                      (const channel))))
+
+(defcustom erc-unhide-query-prompt nil
+  "When non-nil, always reveal query prompts upon reconnecting.
+Otherwise, prompts in a connection's query buffers remain hidden until
+the user types in the input area or a new message arrives from the
+target."
+  :package-version '(ERC . "5.4.1")
+  :group 'erc-display
+  ;; Extensions may one day offer a way to discover whether a target
+  ;; is online.  When that happens, this can be expanded accordingly.
+  :type 'boolean)
 
 ;; tunable GUI stuff
 
@@ -1424,14 +1413,23 @@ if ARG is omitted or nil.
   (string "" :type string :documentation "Received name of target.")
   (symbol nil :type symbol :documentation "Case-mapped name as symbol."))
 
+;; These should probably take on a `joined' field to track joinedness,
+;; which should be toggled by `erc-server-JOIN', `erc-server-PART',
+;; etc.  Functions like `erc--current-buffer-joined-p' (bug#48598) may
+;; find it useful.
+
 (cl-defstruct (erc--target-channel (:include erc--target)))
 
 (cl-defstruct (erc--target-channel-local (:include erc--target-channel)))
 
+;; At some point, it may make sense to add a query type with an
+;; account field, which may help support reassociation across
+;; reconnects and nick changes (likely requires v3 extensions).
+
 (defun erc--target-from-string (string)
   "Construct an `erc--target' variant from STRING."
   (funcall (if (erc-channel-p string)
-               (if (erc-valid-local-channel-p string)
+               (if (erc--valid-local-channel-p string)
                    #'make-erc--target-channel-local
                  #'make-erc--target-channel)
              #'make-erc--target)
@@ -2407,7 +2405,7 @@ Example usage:
 
     (erc-tls :server \"irc.libera.chat\" :port 6697
              :client-certificate
-             '(\"/home/bandali/my-cert.key\"
+             \\='(\"/home/bandali/my-cert.key\"
                \"/home/bandali/my-cert.crt\"))
 
 When present, ID should be an opaque object for identifying the
@@ -3321,39 +3319,38 @@ For a list of user commands (/join /part, ...):
 (defalias 'erc-cmd-H #'erc-cmd-HELP)
 (put 'erc-cmd-HELP 'process-not-needed t)
 
-(defcustom erc-auth-source-parameters-function
-  #'erc--auth-source-determine-params
-  "A function providing args to pass to `auth-source-search'.
-This is called with no arguments and should return a plist of keyword
-args accepted by `auth-source-search'.  The ordering of the pairs
-influences how results are filtered as does the ordering of the members
-of any composite pair values, when applicable.  If necessary, the former
-takes priority over the latter.  For example, if the function returns
-
-  (:host (foo bar) :port (123 456) :require (:secret))
-
-the secret from an auth-source entry of host foo and port 456
-will be chosen over another of host bar and port 123.  However,
-if the function returns
-
-  (:port (123 456) :host (foo bar) :require (:secret))
-
-the opposite will be true.  In both cases, two entries with the same
-host but different ports would see the one with port 123 being selected.
-Much the same would happen for entries sharing only a port: the one with
-host foo would win.
-
-Some auth-source back ends may not be compatible (netrc and pass are
-currently supported)."
-  :package-version '(ERC . "5.4.1") ; FIXME increment upon publishing to ELPA
+(defcustom erc-auth-source-parameters-server-function
+  #'erc-auth-source-determine-params-merge
+  "Server-password filter for `auth-source-search'.
+Called with keyword parameters known to `auth-source-search' and
+relevant to an opening \"PASS\" command, if any.  In return, ERC expects
+an optionally modified set of parameters to use both for the auth-source
+query and for narrowing its results.  A value of nil tells ERC to omit
+the \"PASS\" command completely.  An explicit `:password' argument to
+entry-point commands `erc' and `erc-tls' also inhibits look-up.  See
+info node `(erc) Connecting' for details."
+  :package-version '(ERC . "5.4.1") ; FIXME update when publishing to ELPA
   :group 'erc
-  :type 'function)
+  :type '(choice (const erc-auth-source-determine-params-merge)
+                 (const nil)
+                 function))
+
+(defcustom erc-auth-source-parameters-join-function
+  #'erc-auth-source-determine-params-merge
+  "Channel-join filter for `auth-source-search'.
+Called with keyword parameters known to `auth-source-search' and
+relevant to joining a password-protected channel.  In return, ERC
+expects an optionally modified set of parameters to use both for the
+auth-source query and for narrowing its results.  A value of nil tells
+ERC to forgo consulting auth-source for channel keys.  For more
+information, see info node `(erc) Connecting'."
+  :package-version '(ERC . "5.4.1") ; FIXME update when publishing to ELPA
+  :group 'erc
+  :type '(choice (const erc-auth-source-determine-params-merge)
+                 (const nil)
+                 function))
 
-(defun erc--auth-source-determine-params ()
-  "Return a plist of default args to pass to `auth-source-search'.
-Favor a network ID over an announced server unless `erc--target' is a
-local channel.  Treat the dialed server address as a fallback for the
-announced name in both cases."
+(defun erc--auth-source-determine-params-defaults ()
   (let* ((net (and-let* ((esid (erc-networks--id-symbol erc-networks--id))
                          ((symbol-name esid)))))
          (localp (and erc--target (erc--target-channel-local-p erc--target)))
@@ -3366,44 +3363,47 @@ announced name in both cases."
                                      erc-session-port)) ; or nil
                         (t erc-session-port))
                       "irc")))
-    (list :host (delq nil hosts)
-          :port (delq nil ports)
-          :require '(:secret))))
-
-(declare-function erc-compat--auth-source-pass-backend-parse
-                  "erc-compat" (entry))
-
-(defun erc--auth-source-search (&rest plist)
+    (list (cons :host (delq nil hosts))
+          (cons :port (delq nil ports))
+          (cons :require '(:secret)))))
+
+(defun erc-auth-source-determine-params-merge (&rest plist)
+  "Return a plist of merged keyword args to pass to `auth-source-search'.
+Combine items in PLIST with others derived from the current connection
+context, but prioritize the former.  For keys not present in PLIST,
+favor a network ID over an announced server unless `erc--target' is a
+local channel.  And treat the dialed server address as a fallback for
+the announced name in both cases."
+  (let ((defaults (erc--auth-source-determine-params-defaults)))
+    `(,@(cl-loop for (key value) on plist by #'cddr
+                 for default = (assq key defaults)
+                 do (when default (setq defaults (delq default defaults)))
+                 append `(,key ,(delete-dups
+                                 `(,@(if (consp value) value (list value))
+                                   ,@(cdr default)))))
+      ,@(cl-loop for (k . v) in defaults append (list k v)))))
+
+(defun erc--auth-source-search (&rest defaults)
   "Ask auth-source for a secret and return it if found.
-Favor overrides in PLIST, if any.  Otherwise, use whatever's present in
-the list returned by `erc-auth-source-parameters-function'.  Return a
-string if found or nil otherwise."
-  (let* ((auth-source-backend-parser-functions
-          (if (memq 'password-store auth-sources)
-              (cons #'erc-compat--auth-source-pass-backend-parse
-                    auth-source-backend-parser-functions)
-            auth-source-backend-parser-functions))
-         (defaults (funcall erc-auth-source-parameters-function))
-         priority
-         (test (lambda (a b)
-                 (catch 'done
-                   (dolist (key priority)
-                     (let* ((d (plist-get defaults key))
-                            (default-value (if (listp d) d (list d)))
-                            ;; featurep 'seq via auth-source > json > map
-                            (p (seq-position default-value (plist-get a key)))
-                            (q (seq-position default-value (plist-get b key))))
-                       (unless (eql p q)
-                         (throw 'done (when p (or (not q) (< p q)))))))))))
-    (cl-loop for (key value) on defaults by #'cddr
-             when value unless (plist-get plist key)
-             do (setq plist (plist-put plist key value)))
-    (let ((keys (nreverse (map-keys defaults))))
-      (dolist (key (map-keys plist))
-        (cl-pushnew key keys))
-      (setq priority (nreverse keys)))
-    (unless (plist-get plist :max) ; from `auth-source-netrc-parse'
-      (setq plist (plist-put plist :max 5000)))
+Use DEFAULTS as arguments for querying auth-source and as a guide for
+narrowing the results.  Return a string if found or nil otherwise."
+  (when-let*
+      ((priority (map-keys defaults))
+       (test (lambda (a b)
+               (catch 'done
+                 (dolist (key priority)
+                   (let* ((d (plist-get defaults key))
+                          (defval (if (listp d) d (list d)))
+                          ;; featurep 'seq via auth-source > json > map
+                          (p (seq-position defval (plist-get a key)))
+                          (q (seq-position defval (plist-get b key))))
+                     (unless (eql p q)
+                       (throw 'done (when p (or (not q) (< p q))))))))))
+       (plist (copy-sequence defaults)))
+    (unless (plist-get plist :max)
+      (setq plist (plist-put plist :max 5000))) ; `auth-source-netrc-parse'
+    (unless (plist-get defaults :require)
+      (setq plist (plist-put plist :require '(:secret))))
     (when-let* ((sorted (sort (apply #'auth-source-search plist) test))
                 (secret (plist-get (car sorted) :secret)))
       (if (functionp secret) (funcall secret) secret))))
@@ -3411,16 +3411,18 @@ string if found or nil otherwise."
 (defun erc-server-join-channel (server channel &optional secret)
   "Join CHANNEL, optionally with SECRET.
 Without SECRET, consult auth source, using SERVER if non-nil."
-  (unless secret
+  (unless (or secret (not erc-auth-source-parameters-join-function))
     (unless server
-      (when (and erc-server-announced-name (erc-valid-local-channel-p channel))
+      (when (and erc-server-announced-name
+                 (erc--valid-local-channel-p channel))
         (setq server erc-server-announced-name)))
-    (let ((args `(,@(when server (list :host server)) :user channel)))
+    (let ((args (apply erc-auth-source-parameters-join-function
+                       `(,@(and server (list :host server)) :user channel))))
       (setq secret (apply #'erc--auth-source-search args))))
   (erc-log (format "cmd: JOIN: %s" channel))
   (erc-server-send (concat "JOIN " channel (when secret (concat " " secret)))))
 
-(defun erc-valid-local-channel-p (channel)
+(defun erc--valid-local-channel-p (channel)
   "Non-nil when channel is server-local on a network that allows them."
   (and-let* (((eq ?& (aref channel 0)))
              (chan-types (erc--get-isupport-entry 'CHANTYPES 'single))
@@ -4955,7 +4957,14 @@ Set user modes and run `erc-after-connect' hook."
         (erc-update-mode-line)
         (erc-set-initial-user-mode nick buffer)
         (erc-server-setup-periodical-ping buffer)
-        (run-hook-with-args 'erc-after-connect server nick)))))
+        (run-hook-with-args 'erc-after-connect server nick))))
+
+  (when erc-unhide-query-prompt
+    (erc-with-all-buffers-of-server proc
+      nil ; FIXME use `erc--target' after bug#48598
+      (when (and (erc-default-target)
+                 (not (erc-channel-p (car erc-default-recipients))))
+        (erc--unhide-prompt)))))
 
 (defun erc-set-initial-user-mode (nick buffer)
   "If `erc-user-mode' is non-nil for NICK, set the user modes.
@@ -6416,7 +6425,7 @@ Sets the buffer local variables:
         erc-session-port (or port erc-default-port)
         erc-session-user-full-name (erc-compute-full-name name)
         erc-session-username (erc-compute-user user)
-        erc-session-password (erc-compute-server-password passwd nick))
+        erc-session-password (erc--compute-server-password passwd nick))
   (erc-set-current-nick (erc-compute-nick nick)))
 
 (defun erc-compute-server (&optional server)
@@ -6453,19 +6462,15 @@ non-nil value is found.
       (getenv "IRCNICK")
       (user-login-name)))
 
-(defun erc-compute-server-password (password nick)
-  "Determine initial PASSWORD value for IRC PASS command.
-Use the value of `erc-connect-auth-source-host' to determine the
-machine/host query param.  Use NICK for the user/login query param."
+(defun erc--compute-server-password (password nick)
+  "Maybe provide a PASSWORD argument for the IRC \"PASS\" command.
+Use NICK for the user field when querying auth-source.  Defer to
+`erc-auth-source-parameters-server-function' for the host param."
   (or password
-      (when erc-connect-auth-source-host
-        (let* ((host (pcase erc-connect-auth-source-host
-                       ('server erc-session-server)
-                       ((and (pred stringp) v) v)
-                       ('prompt (read-string "Auth-source host: "
-                                             nil t (list nil)))))
-               (args `(,@(when host (list :host host)) :user ,nick)))
-          (apply #'erc--auth-source-search args)))))
+      (and erc-auth-source-parameters-server-function
+           (apply #'erc--auth-source-search
+                  (funcall erc-auth-source-parameters-server-function
+                           :user nick)))))
 
 (defun erc-compute-full-name (&optional full-name)
   "Return user's full name.
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/proxy-subprocess.el 
b/test/lisp/erc/erc-d/erc-d-self-resources/proxy-subprocess.el
deleted file mode 100644
index 6e4624050a..0000000000
--- a/test/lisp/erc/erc-d/erc-d-self-resources/proxy-subprocess.el
+++ /dev/null
@@ -1,26 +0,0 @@
-;;; proxy-subprocess.el --- Example setup file for erc-d
-;;; Commentary:
-;;; Code:
-
-(defvar erc-d-spec-vars)
-
-(setq erc-d-spec-vars
-
-      (list
-       (cons 'fqdn (lambda (helper)
-                     (let ((name (funcall helper :dialog-name)))
-                       (funcall helper :set
-                                (if (eq name 'proxy-foonet)
-                                    "irc.foo.net"
-                                  "irc.bar.net")))))
-
-       (cons 'net (lambda (helper)
-                    (let ((name (funcall helper :dialog-name)))
-                      (funcall helper :set
-                               (if (eq name 'proxy-foonet)
-                                   "FooNet"
-                                 "BarNet")))))
-
-       (cons 'network '(group (+ alpha)))))
-
-;;; proxy-subprocess.el ends here
diff --git a/test/lisp/erc/erc-join-tests.el b/test/lisp/erc/erc-join-tests.el
index e9c432b4a2..2eb20ffd8d 100644
--- a/test/lisp/erc/erc-join-tests.el
+++ b/test/lisp/erc/erc-join-tests.el
@@ -30,7 +30,7 @@
 
   (let (calls
         common
-        erc-kill-server-hook)
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
 
     (cl-letf (((symbol-function 'erc-server-send)
                (lambda (line) (push line calls))))
@@ -78,7 +78,7 @@
 
   (let (calls
         common
-        erc-kill-server-hook
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook
         (erc-autojoin-timing 'ident)
         (erc-autojoin-delay 0.05))
 
@@ -127,7 +127,7 @@
 
   (let (calls
         common
-        erc-kill-server-hook
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook
         (erc-autojoin-timing 'ident))
 
     (cl-letf (((symbol-function 'erc-server-send)
@@ -157,9 +157,10 @@
           (funcall common))
         (should (equal (pop calls) "JOIN #chan"))))))
 
-(defun erc-join-tests--autojoin-add--common (setup)
+(defun erc-join-tests--autojoin-add--common (setup &optional fwd)
   (let (calls
-        erc-autojoin-channels-alist)
+        erc-autojoin-channels-alist
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
 
     (cl-letf (((symbol-function 'erc-handle-parsed-server-response)
                (lambda (_p m) (push m calls))))
@@ -178,14 +179,16 @@
 
         (ert-info ("Add #chan")
           (erc-parse-server-response erc-server-process
-                                     ":tester!~i@c.u JOIN #chan")
+                                     (concat ":tester!~i@c.u JOIN #chan"
+                                             (and fwd " * :Tes Ter")))
           (should calls)
           (erc-autojoin-add erc-server-process (pop calls))
           (should (equal erc-autojoin-channels-alist '((FooNet "#chan")))))
 
         (ert-info ("More recently joined chans are prepended")
-          (erc-parse-server-response erc-server-process
-                                     ":tester!~i@c.u JOIN #spam")
+          (erc-parse-server-response
+           erc-server-process ; with account username
+           (concat ":tester!~i@c.u JOIN #spam" (and fwd " tester :Tes Ter")))
           (should calls)
           (erc-autojoin-add erc-server-process (pop calls))
           (should (equal erc-autojoin-channels-alist
@@ -193,7 +196,8 @@
 
         (ert-info ("Duplicates skipped")
           (erc-parse-server-response erc-server-process
-                                     ":tester!~i@c.u JOIN #chan")
+                                     (concat ":tester!~i@c.u JOIN #chan"
+                                             (and fwd " * :Tes Ter")))
           (should calls)
           (erc-autojoin-add erc-server-process (pop calls))
           (should (equal erc-autojoin-channels-alist
@@ -201,7 +205,8 @@
 
         (ert-info ("Server used for local channel")
           (erc-parse-server-response erc-server-process
-                                     ":tester!~i@c.u JOIN &local")
+                                     (concat ":tester!~i@c.u JOIN &local"
+                                             (and fwd " * :Tes Ter")))
           (should calls)
           (erc-autojoin-add erc-server-process (pop calls))
           (should (equal erc-autojoin-channels-alist
@@ -213,6 +218,12 @@
    (lambda () (setq erc-network 'FooNet
                     erc-networks--id (erc-networks--id-create nil)))))
 
+(ert-deftest erc-autojoin-add--network-extended-syntax ()
+  (erc-join-tests--autojoin-add--common
+   (lambda () (setq erc-network 'FooNet
+                    erc-networks--id (erc-networks--id-create nil)))
+   'forward-compatible))
+
 (ert-deftest erc-autojoin-add--network-id ()
   (erc-join-tests--autojoin-add--common
    (lambda () (setq erc-network 'invalid
@@ -220,7 +231,8 @@
 
 (ert-deftest erc-autojoin-add--server ()
   (let (calls
-        erc-autojoin-channels-alist)
+        erc-autojoin-channels-alist
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
 
     (cl-letf (((symbol-function 'erc-handle-parsed-server-response)
                (lambda (_p m) (push m calls))))
@@ -245,7 +257,8 @@
 
 (defun erc-join-tests--autojoin-remove--common (setup)
   (let (calls
-        erc-autojoin-channels-alist)
+        erc-autojoin-channels-alist
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
 
     (cl-letf (((symbol-function 'erc-handle-parsed-server-response)
                (lambda (_p m) (push m calls))))
@@ -307,7 +320,8 @@
 
 (ert-deftest erc-autojoin-remove--server ()
   (let (calls
-        erc-autojoin-channels-alist)
+        erc-autojoin-channels-alist
+        erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook)
 
     (cl-letf (((symbol-function 'erc-handle-parsed-server-response)
                (lambda (_p m) (push m calls))))
diff --git a/test/lisp/erc/erc-scenarios-common.el 
b/test/lisp/erc/erc-scenarios-common.el
deleted file mode 100644
index 9d81c0ee74..0000000000
--- a/test/lisp/erc/erc-scenarios-common.el
+++ /dev/null
@@ -1,152 +0,0 @@
-;;; erc-scenarios-common.el --- common helpers for ERC scenarios -*- 
lexical-binding: t -*-
-
-;; Copyright (C) 2021 Free Software Foundation, Inc.
-;;
-;; This file is part of GNU Emacs.
-;;
-;; This program 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.
-;;
-;; This program 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 this program.  If not, see
-;; <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; This file should not contain any test cases.
-
-(require 'ert-x) ; cl-lib
-
-(eval-and-compile (let ((dir (getenv "EMACS_TEST_DIRECTORY")))
-                    (when dir
-                      (load (concat dir "/lisp/erc/erc-d/erc-d-t") nil t)
-                      (load (concat dir "/lisp/erc/erc-d/erc-d") nil t))))
-(require 'erc-d)
-(require 'erc-d-t)
-(require 'erc-backend)
-
-(defvar erc-scenarios-common--resources-dir
-  (expand-file-name (concat (ert-resource-directory)
-                            "../erc-scenarios-resources/")))
-
-;; Because teardown is already inhibited when running interactively,
-;; which prevents subsequent tests from succeeding, we might as well
-;; treat inspection as the goal.
-(unless noninteractive
-  (setq erc-server-auto-reconnect nil))
-
-(defvar erc-scenarios-common-dialog nil)
-(defvar erc-scenarios-common-extra-teardown nil)
-
-(defun erc-scenarios-common--add-silence ()
-  (advice-add #'erc-login :around #'erc-d-t-silence-around)
-  (advice-add #'erc-handle-login :around #'erc-d-t-silence-around)
-  (advice-add #'erc-server-connect :around #'erc-d-t-silence-around))
-
-(defun erc-scenarios-common--remove-silence ()
-  (advice-remove #'erc-login #'erc-d-t-silence-around)
-  (advice-remove #'erc-handle-login #'erc-d-t-silence-around)
-  (advice-remove #'erc-server-connect #'erc-d-t-silence-around))
-
-(defun erc-scenarios-common--print-trace ()
-  (when (and (boundp 'trace-buffer) (get-buffer trace-buffer))
-    (with-current-buffer trace-buffer
-      (message "%S" (buffer-string))
-      (kill-buffer))))
-
-(defun erc-scenarios-common--make-bindings (bindings)
-  `((erc-d-u-canned-dialog-dir (expand-file-name
-                                (or erc-scenarios-common-dialog
-                                    (cadr (assq 'erc-scenarios-common-dialog
-                                                ',bindings)))
-                                erc-scenarios-common--resources-dir))
-    (erc-d-spec-vars `(,@erc-d-spec-vars
-                       (quit . ,(erc-quit/part-reason-default))
-                       (erc-version . ,erc-version)))
-    (erc-modules (copy-sequence erc-modules))
-    (inhibit-interaction t)
-    (auth-source-do-cache nil)
-    (erc-autojoin-channels-alist nil)
-    (erc-server-auto-reconnect nil)
-    ,@bindings))
-
-(defmacro erc-scenarios-common-with-cleanup (bindings &rest body)
-  "Provide boilerplate cleanup tasks after calling BODY with BINDINGS.
-
-If an `erc-d' process exists, wait for it to start before running BODY.
-If `erc-autojoin-mode' mode is bound, restore it during cleanup if
-disabled by BODY.  Other defaults common to these test cases are added
-below and can be overridden, except when wanting the \"real\" default
-value, which must be looked up or captured outside of the calling form.
-
-Dialog resource directories are located by expanding the variable
-`erc-scenarios-common-dialog' or its value in BINDINGS."
-  (declare (indent 1))
-
-  (let* ((orig-autojoin-mode (make-symbol "orig-autojoin-mode"))
-         (combind `((,orig-autojoin-mode (bound-and-true-p erc-autojoin-mode))
-                    ,@(erc-scenarios-common--make-bindings bindings))))
-
-    `(erc-d-t-with-cleanup (,@combind)
-
-         (ert-info ("Restore autojoin, etc., kill ERC buffers")
-           (dolist (buf (buffer-list))
-             (when-let ((erc-d-u--process-buffer)
-                        (proc (get-buffer-process buf)))
-               (erc-d-t-wait-for 5 "Dumb server dies on its own"
-                 (not (process-live-p proc)))))
-
-           (erc-scenarios-common--remove-silence)
-
-           (when erc-scenarios-common-extra-teardown
-             (ert-info ("Running extra teardown")
-               (funcall erc-scenarios-common-extra-teardown)))
-
-           (when (and (boundp 'erc-autojoin-mode)
-                      (not (eq erc-autojoin-mode ,orig-autojoin-mode)))
-             (erc-autojoin-mode (if ,orig-autojoin-mode +1 -1)))
-
-           (when noninteractive
-             (erc-scenarios-common--print-trace)
-             (erc-d-t-kill-related-buffers)))
-
-       (erc-scenarios-common--add-silence)
-
-       (ert-info ("Wait for dumb server")
-         (dolist (buf (buffer-list))
-           (with-current-buffer buf
-             (when erc-d-u--process-buffer
-               (erc-d-t-search-for 3 "Starting")))))
-
-       (ert-info ("Activate erc-debug-irc-protocol")
-         (unless (and noninteractive (not erc-debug-irc-protocol))
-           (erc-toggle-debug-irc-protocol)))
-
-       ,@body)))
-
-(defun erc-scenarios-common-assert-initial-buf-name (id port)
-  ;; Assert no limbo period when explicit ID given
-  (should (string= (if id
-                       (symbol-name id)
-                     (format "127.0.0.1:%d" port))
-                   (buffer-name))))
-
-(defun erc-scenarios-common-buflist (prefix)
-  "Return list of buffers with names sharing PREFIX."
-  (let (case-fold-search)
-    (erc-networks--id-sort-buffers
-     (delq nil
-           (mapcar (lambda (b)
-                     (when (string-prefix-p prefix (buffer-name b)) b))
-                   (buffer-list))))))
-
-(provide 'erc-scenarios-common)
-
-;;; erc-scenarios-common.el ends here
diff --git a/test/lisp/erc/erc-scenarios.el b/test/lisp/erc/erc-scenarios.el
deleted file mode 100644
index d206e4835c..0000000000
--- a/test/lisp/erc/erc-scenarios.el
+++ /dev/null
@@ -1,2312 +0,0 @@
-;;; erc-scenarios.el --- user test cases for ERC -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 Free Software Foundation, Inc.
-;;
-;; This file is part of GNU Emacs.
-;;
-;; This program 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.
-;;
-;; This program 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 this program.  If not, see
-;; <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-;;
-;; These are e2e-ish test cases primarily intended to assert core,
-;; fundamental behavior expected of any modern IRC client.  Tests may
-;; also simulate specific scenarios drawn from bug reports.  Incoming
-;; messages are provided by playback scripts resembling I/O logs.  In
-;; place of time stamps, they have time deltas, which are used to
-;; govern the test server in a fashion reminiscent of music rolls (or
-;; the script(1) UNIX program).  These scripts can be found in the
-;; accompanying erc-scenarios-resources directory.
-;;
-;; Isolation:
-;;
-;; The set of enabled modules is shared among all tests.  The function
-;; `erc-update-modules' activates them (as minor modes), but it never
-;; deactivates them.  So there's no going back, and let-binding
-;; `erc-modules' is useless.  The safest route is therefore to (1)
-;; assume the set of default modules is already activated or will be
-;; over the course of the test session and (2) let-bind relevant user
-;; options as needed.  For example, to limit the damage of
-;; `erc-autojoin-channels-alist' to a given test, assume the
-;; `erc-join' library has already been loaded or will be on the next
-;; call to `erc-open'.  And then simply let-bind
-;; `erc-autojoin-channels-alist' for the duration of the test.
-;;
-;; Playing nice:
-;;
-;; Right now, these tests all rely on an ugly fixture macro named
-;; `erc-scenarios-common-with-cleanup', which is defined in the
-;; companion file erc-scenarios-common.el.  It helps restore (but not
-;; really prepare) the environment by destroying any stray processes
-;; or buffers named in the first argument, a `let*'-style VAR-LIST.
-;; Relying on such a macro is unfortunate because in many ways it
-;; actually hampers readability by favoring magic over verbosity.  But
-;; without it (or something similar), any failing test would cause all
-;; subsequent tests in this file to fail in a cascading manner (making
-;; all but the first backtrace useless).
-;;
-;; Misc:
-;;
-;; Note that in the following examples, nicknames Alice and Bob are
-;; always associated with the fake network FooNet, while nicks Joe and
-;; Mike are always on BarNet.
-;;
-
-;;; Code:
-(require 'ert-x) ; cl-lib
-
-(eval-and-compile
-  (let ((dir (getenv "EMACS_TEST_DIRECTORY")))
-    (when dir (load (concat dir "/lisp/erc/erc-scenarios-common") nil t))))
-
-(require 'erc-d)
-(require 'erc-scenarios-common)
-(require 'erc)
-(eval-when-compile (require 'erc-services))
-
-(declare-function erc-network-name "erc-networks")
-(declare-function erc-network "erc-networks")
-(defvar erc-autojoin-channels-alist)
-(defvar erc-network)
-
-;; Two networks, same channel name, no confusion (no bouncer).  Some
-;; of this draws from bug#47522 "foil-in-server-buf".  It shows that
-;; disambiguation-related changes added for bug#48598 are not specific
-;; to bouncers.
-
-(defun erc-scenarios-common--base-association-multi-net (second-join)
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/association/multi-net")
-       (erc-server-flood-penalty 0.1)
-       (erc-d-linger-secs 1)
-       (dumb-server-foonet-buffer (get-buffer-create "*server-foonet*"))
-       (dumb-server-barnet-buffer (get-buffer-create "*server-barnet*"))
-       (dumb-server-foonet (erc-d-run "localhost" t "server-foonet" 'foonet))
-       (dumb-server-barnet (erc-d-run "localhost" t "server-barnet" 'barnet))
-       (expect (erc-d-t-make-expecter)))
-
-    (ert-info ("Connect to foonet, join #chan")
-      (with-current-buffer
-          (erc :server "127.0.0.1"
-               :port (process-contact dumb-server-foonet :service)
-               :nick "tester"
-               :password "changeme"
-               :full-name "tester")
-        (funcall expect 3 "debug mode")
-        (erc-cmd-JOIN "#chan")))
-
-    (erc-d-t-wait-for 2 (get-buffer "#chan"))
-
-    (ert-info ("Connect to barnet, join #chan")
-      (with-current-buffer
-          (erc :server "127.0.0.1"
-               :port (process-contact dumb-server-barnet :service)
-               :nick "tester"
-               :password "changeme"
-               :full-name "tester")
-        (funcall expect 1 "debug mode")))
-
-    (funcall second-join)
-
-    (erc-d-t-wait-for 3 (get-buffer "#chan@barnet"))
-
-    (erc-d-t-wait-for 2 "Buf #chan now #chan@foonet"
-      (and (get-buffer "#chan@foonet") (not (get-buffer "#chan"))))
-
-    (ert-info ("All #chan@foonet output consumed")
-      (with-current-buffer "#chan@foonet"
-        (funcall expect 3 "bob")
-        (funcall expect 3 "was created on")
-        (funcall expect 3 "prosperous")))
-
-    (ert-info ("All #chan@barnet output consumed")
-      (with-current-buffer "#chan@barnet"
-        (funcall expect 3 "mike")
-        (funcall expect 3 "was created on")
-        (funcall expect 3 "ingenuous")))))
-
-(ert-deftest erc-scenarios-base-association-multi-net--baseline ()
-  (erc-scenarios-common--base-association-multi-net
-   (lambda () (with-current-buffer "barnet" (erc-cmd-JOIN "#chan")))))
-
-;; The /join command only targets the current buffer's process.  This
-;; recasts scenario bug#48598 "ambiguous-join" (which was based on
-;; bug#47522) to show that issuing superfluous /join commands
-;; (apparently fairly common) is benign.
-
-(ert-deftest erc-scenarios-base-association-multi-net--ambiguous-join ()
-  (erc-scenarios-common--base-association-multi-net
-   (lambda ()
-     (ert-info ("Nonsensical JOIN attempts silently dropped.")
-       (with-current-buffer "foonet" (erc-cmd-JOIN "#chan"))
-       (sit-for 0.1)
-       (with-current-buffer "#chan" (erc-cmd-JOIN "#chan"))
-       (sit-for 0.1)
-       (erc-d-t-wait-for 2 (get-buffer "#chan"))
-       (erc-d-t-wait-for 1 "Only one #chan buffer exists"
-         (should (equal (erc-scenarios-common-buflist "#chan")
-                        (list (get-buffer "#chan")))))
-       (with-current-buffer "*server-barnet*"
-         (erc-d-t-absent-for 0.1 "JOIN"))
-       (with-current-buffer "barnet" (erc-cmd-JOIN "#chan"))))))
-
-;; One network, two simultaneous connections, no IDs.
-;; Reassociates on reconnect with and without server buffer.
-
-(defun erc-scenarios-common--base-association-same-network (after)
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/association/same-network")
-       (dumb-server (erc-d-run "localhost" t 'tester 'chester 'tester-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.5)
-       (erc-server-flood-margin 30))
-
-    (ert-info ("Connect to foonet with nick tester")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :password "changeme"
-                                :full-name "tester")
-        (erc-scenarios-common-assert-initial-buf-name nil port)
-        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
-
-    (ert-info ("Connect to foonet with nick chester")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "chester"
-                                :password "changeme"
-                                :full-name "chester")
-        (erc-scenarios-common-assert-initial-buf-name nil port)))
-
-    (erc-d-t-wait-for 3 "Dialed Buflist is Empty"
-      (not (erc-scenarios-common-buflist "127.0.0.1")))
-
-    (with-current-buffer "foonet/tester"
-      (funcall expect 3 "debug mode")
-      (erc-cmd-JOIN "#chan"))
-
-    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/tester"))
-    (with-current-buffer "foonet/chester" (funcall expect 3 "debug mode"))
-    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/chester"))
-
-    (ert-info ("Nick tester sees other nick chester in channel")
-      (with-current-buffer "#chan@foonet/tester"
-        (funcall expect 5 "chester")
-        (funcall expect 5 "find the forester")
-        (erc-cmd-QUIT "")))
-
-    (ert-info ("Nick chester sees other nick tester in same channel")
-      (with-current-buffer  "#chan@foonet/chester"
-        (funcall expect 5 "tester")
-        (funcall expect 5 "find the forester")))
-
-    (funcall after expect)))
-
-(ert-deftest erc-scenarios-base-association-same-network--reconnect-one ()
-  (erc-scenarios-common--base-association-same-network
-   (lambda (expect)
-
-     (ert-info ("Connection tester reconnects")
-       (with-current-buffer "foonet/tester"
-         (erc-d-t-wait-for 10 (not (erc-server-process-alive)))
-         (funcall expect 10 "*** ERC finished")
-         (erc-cmd-RECONNECT)
-         (funcall expect 5 "debug mode")))
-
-     (ert-info ("Reassociated to same channel")
-       (with-current-buffer "#chan@foonet/tester"
-         (funcall expect 5 "chester")
-         (funcall expect 5 "welcome again")
-         (erc-cmd-QUIT "")))
-
-     (with-current-buffer "#chan@foonet/chester"
-       (funcall expect 5 "tester")
-       (funcall expect 5 "welcome again")
-       (funcall expect 5 "welcome again")
-       (erc-cmd-QUIT "")))))
-
-(ert-deftest erc-scenarios-base-association-same-network--new-buffer ()
-  (erc-scenarios-common--base-association-same-network
-   (lambda (expect)
-
-     (ert-info ("Tester kills buffer and connects from scratch")
-
-       (let (port)
-         (with-current-buffer "foonet/tester"
-           (erc-d-t-wait-for 10 (not (erc-server-process-alive)))
-           (funcall expect 10 "*** ERC finished")
-           (setq port erc-session-port)
-           (kill-buffer))
-
-         (with-current-buffer (erc :server "127.0.0.1"
-                                   :port port
-                                   :nick "tester"
-                                   :password "changeme"
-                                   :full-name "tester")
-
-           (erc-d-t-wait-for 5 (eq erc-network 'foonet)))))
-
-     (with-current-buffer "foonet/tester" (funcall expect 3 "debug mode"))
-
-     (ert-info ("Reassociated to same channel")
-       (with-current-buffer "#chan@foonet/tester"
-         (funcall expect 5 "chester")
-         (funcall expect 5 "welcome again")
-         (erc-cmd-QUIT "")))
-
-     (with-current-buffer "#chan@foonet/chester"
-       (funcall expect 5 "tester")
-       (funcall expect 5 "welcome again")
-       (funcall expect 5 "welcome again")
-       (erc-cmd-QUIT "")))))
-
-;; XXX this is okay, but we also need to check that target buffers are
-;; already associated with a new process *before* a JOIN is sent by a
-;; server's playback burst.  This doesn't do that.
-;;
-;; This *does* check that superfluous JOINs sent by the autojoin
-;; module are harmless when they're not acked (superfluous because the
-;; bouncer/server intitates the JOIN).
-
-(defun erc-scenarios-common--join-network-id (foo-reconnector foo-id bar-id)
-  "Ensure channels rejoined by erc-join.el DTRT.
-Originally from scenario clash-of-chans/autojoin as described in
-Bug#48598: 28.0.50; buffer-naming collisions involving bouncers in ERC."
-  (erc-scenarios-common-with-cleanup
-      ((chan-buf-foo (format "#chan@%s" (or foo-id "foonet")))
-       (chan-buf-bar (format "#chan@%s" (or bar-id "barnet")))
-       (erc-scenarios-common-dialog "join/network-id")
-       (erc-d-t-cleanup-sleep-secs 1)
-       (erc-server-flood-penalty 0.5)
-       (dumb-server (erc-d-run "localhost" t 'foonet 'barnet 'foonet-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       erc-server-buffer-foo erc-server-process-foo
-       erc-server-buffer-bar erc-server-process-bar)
-
-    (should (memq 'autojoin erc-modules))
-
-    (ert-info ("Connect to foonet")
-      (with-current-buffer
-          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "foonet:changeme"
-                                           :full-name "tester"
-                                           :id foo-id))
-        (setq erc-server-process-foo erc-server-process)
-        (erc-scenarios-common-assert-initial-buf-name foo-id port)
-        (erc-d-t-wait-for 1 (eq (erc-network) 'foonet))
-        (funcall expect 5 "foonet")))
-
-    (ert-info ("Join #chan, find sentinel, quit")
-      (with-current-buffer erc-server-buffer-foo (erc-cmd-JOIN "#chan"))
-      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
-        (funcall expect 5 "vile thing")
-        (erc-cmd-QUIT "")))
-
-    (erc-d-t-wait-for 2 "Foonet connection deceased"
-      (not (erc-server-process-alive erc-server-buffer-foo)))
-
-    (should (equal erc-autojoin-channels-alist
-                   (if foo-id '((oofnet "#chan")) '((foonet "#chan")))))
-
-    (ert-info ("Connect to barnet")
-      (with-current-buffer
-          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "barnet:changeme"
-                                           :full-name "tester"
-                                           :id bar-id))
-        (setq erc-server-process-bar erc-server-process)
-        (erc-d-t-wait-for 5 (eq erc-network 'barnet))
-        (should (string= (buffer-name) (if bar-id "rabnet" "barnet")))))
-
-    (ert-info ("Server buffers are unique, no stray IP-based names")
-      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
-      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
-
-    (ert-info ("Only one #chan buffer exists")
-      (should (equal (list (get-buffer "#chan"))
-                     (erc-scenarios-common-buflist "#chan"))))
-
-    (ert-info ("#chan is not auto-joined")
-      (with-current-buffer "#chan"
-        (erc-d-t-absent-for 0.1 "<joe>")
-        (should-not (process-live-p erc-server-process))
-        (erc-d-t-ensure-for 0.1 "server buffer remains foonet"
-          (eq erc-server-process erc-server-process-foo))))
-
-    (with-current-buffer erc-server-buffer-bar
-      (erc-cmd-JOIN "#chan")
-      (erc-d-t-wait-for 3 (get-buffer chan-buf-foo))
-      (erc-d-t-wait-for 3 (get-buffer chan-buf-bar))
-      (with-current-buffer chan-buf-bar
-        (erc-d-t-wait-for 3 (eq erc-server-process erc-server-process-bar))
-        (funcall expect 5 "marry her instantly")))
-
-    (ert-info ("Reconnect to foonet")
-      (with-current-buffer (setq erc-server-buffer-foo
-                                 (funcall foo-reconnector))
-        (should (member (if foo-id '(oofnet "#chan") '(foonet "#chan"))
-                        erc-autojoin-channels-alist))
-        (erc-d-t-wait-for 3 (erc-server-process-alive))
-        (setq erc-server-process-foo erc-server-process)
-        (erc-d-t-wait-for 2 (eq erc-network 'foonet))
-        (should (string= (buffer-name) (if foo-id "oofnet" "foonet")))
-        (funcall expect 5 "foonet")))
-
-    (ert-info ("#chan@foonet is clean, no cross-contamination")
-      (with-current-buffer chan-buf-foo
-        (erc-d-t-wait-for 3 (eq erc-server-process erc-server-process-foo))
-        (funcall expect 3 "<bob>")
-        (erc-d-t-absent-for 0.1 "<joe>")
-        (while (accept-process-output erc-server-process-foo))
-        (funcall expect 3 "not given me")))
-
-    (ert-info ("All #chan@barnet output received")
-      (with-current-buffer chan-buf-bar
-        (while (accept-process-output erc-server-process-bar))
-        (funcall expect 3 "hath an uncle here")))))
-
-(ert-deftest erc-scenarios-join-network-id--cmd-reconnect ()
-  (let ((connect (lambda ()
-                   (with-current-buffer "foonet"
-                     (erc-cmd-RECONNECT)
-                     (should (eq (current-buffer)
-                                 (process-buffer erc-server-process)))
-                     (current-buffer)))))
-    (erc-scenarios-common--join-network-id connect nil nil)))
-
-(ert-deftest erc-scenarios-join-network-id--cmd-reconnect-id ()
-  (let ((connect (lambda ()
-                   (with-current-buffer "oofnet"
-                     (erc-cmd-RECONNECT)
-                     (should (eq (current-buffer)
-                                 (process-buffer erc-server-process)))
-                     (current-buffer)))))
-    (erc-scenarios-common--join-network-id connect 'oofnet nil)))
-
-(ert-deftest erc-scenarios-join-network-id--cmd-reconnect-ids ()
-  (let ((connect (lambda ()
-                   (with-current-buffer "oofnet"
-                     (erc-cmd-RECONNECT)
-                     (should (eq (current-buffer)
-                                 (process-buffer erc-server-process)))
-                     (current-buffer)))))
-    (erc-scenarios-common--join-network-id connect 'oofnet 'rabnet)))
-
-(ert-deftest erc-scenarios-join-network-id--new-invocation ()
-  (let ((connect (lambda ()
-                   (erc :server "127.0.0.1"
-                        :port (with-current-buffer "foonet"
-                                (process-contact erc-server-process :service))
-                        :nick "tester"
-                        :password "foonet:changeme"
-                        :full-name "tester"))))
-    (erc-scenarios-common--join-network-id connect nil nil)))
-
-(ert-deftest erc-scenarios-join-network-id--new-invocation-id ()
-  (let ((connect (lambda ()
-                   (erc :server "127.0.0.1"
-                        :port (with-current-buffer "oofnet"
-                                (process-contact erc-server-process :service))
-                        :nick "tester"
-                        :password "foonet:changeme"
-                        :full-name "tester"
-                        :id 'oofnet))))
-    (erc-scenarios-common--join-network-id connect 'oofnet nil)))
-
-(ert-deftest erc-scenarios-join-network-id--new-invocation-ids ()
-  (let ((connect (lambda ()
-                   (erc :server "127.0.0.1"
-                        :port (with-current-buffer "oofnet"
-                                (process-contact erc-server-process :service))
-                        :nick "tester"
-                        :password "foonet:changeme"
-                        :full-name "tester"
-                        :id 'oofnet))))
-    (erc-scenarios-common--join-network-id connect 'oofnet 'rabnet)))
-
-;; Ensure the old way of specifying a partial domain name still works.
-
-(ert-deftest erc-scenarios-base-legacy-autojoin--announced ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "join/legacy")
-       (erc-d-linger-secs 1)
-       (erc-server-flood-penalty 0.1)
-       (dumb-server (erc-d-run "localhost" t 'foonet))
-       (port (process-contact dumb-server :service))
-       (erc-autojoin-channels-alist '(("libera\\.chat" "#erc")
-                                      ("foonet\\.org" "#chan"))))
-
-    (ert-info ("Connect")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :password "changeme"
-                                :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (erc-d-t-wait-for 1 (get-buffer "FooNet"))
-
-    (ert-info ("Channel buffer #chan autojoined")
-      (with-current-buffer (erc-d-t-wait-for 6 (get-buffer "#chan"))
-        (erc-d-t-search-for 10 "Live, and be prosperous")))))
-
-(ert-deftest erc-scenarios-join-reconnect ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "join/reconnect")
-       (dumb-server (erc-d-run "localhost" t 'foonet 'foonet-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.1)
-       (erc-server-auto-reconnect t)
-       erc-autojoin-channels-alist
-       erc-server-buffer)
-
-    (should (memq 'autojoin erc-modules))
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer (erc :server "127.0.0.1"
-                                   :port port
-                                   :nick "tester"
-                                   :password "changeme"
-                                   :full-name "tester"))
-      (with-current-buffer erc-server-buffer
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (funcall expect 1 "debug mode")))
-
-    (ert-info ("Wait for some output in channels")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
-        (funcall expect 10 "welcome"))
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
-        (funcall expect 10 "welcome")))
-
-    (should (equal erc-autojoin-channels-alist '((FooNet "#spam" "#chan"))))
-
-    (ert-info ("Wait for auto reconnect")
-      (with-current-buffer erc-server-buffer
-        (funcall expect 10 "still in debug mode")))
-
-    (ert-info ("Wait for activity to recommence in channels")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
-        (funcall expect 10 "forest of Arden"))
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
-        (funcall expect 10 "her elves come here anon")))))
-
-;; Playback for same channel on two networks routed correctly.
-;; Originally from Bug#48598: 28.0.50; buffer-naming collisions
-;; involving bouncers in ERC.
-
-(ert-deftest erc-scenarios-base-association-bouncer-history ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/association/bouncer-history")
-       (erc-d-t-cleanup-sleep-secs 1)
-       (erc-d-linger-secs 1)
-       (dumb-server (erc-d-run "localhost" t 'foonet 'barnet))
-       (port (process-contact dumb-server :service))
-       (erc-server-flood-penalty 0.5)
-       (expect (erc-d-t-make-expecter))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo erc-server-process-foo
-       erc-server-buffer-bar erc-server-process-bar)
-
-    (ert-info ("Connect to foonet")
-      (with-current-buffer
-          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "foonet:changeme"
-                                           :full-name "tester"))
-        (setq erc-server-process-foo erc-server-process)
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (funcall expect 5 "foonet")))
-
-    (erc-d-t-wait-for 5 (get-buffer "#chan"))
-
-    (ert-info ("Connect to barnet")
-      (with-current-buffer
-          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "barnet:changeme"
-                                           :full-name "tester"))
-        (setq erc-server-process-bar erc-server-process)
-        (erc-d-t-wait-for 5 "Temporary name assigned"
-          (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (funcall expect 5 "barnet")))
-
-    (ert-info ("Server buffers are unique")
-      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar)))
-
-    (ert-info ("Networks correctly determined and adopted as buffer names")
-      (with-current-buffer erc-server-buffer-foo
-        (erc-d-t-wait-for 3 "network name foonet becomes buffer name"
-          (and (eq (erc-network) 'foonet) (string= (buffer-name) "foonet"))))
-      (with-current-buffer erc-server-buffer-bar
-        (erc-d-t-wait-for 3 "network name barnet becomes buffer name"
-          (and (eq (erc-network) 'barnet) (string= (buffer-name) "barnet")))))
-
-    (erc-d-t-wait-for 5 (get-buffer "#chan@barnet"))
-
-    (ert-info ("Two channel buffers created, original #chan renamed")
-      (should (= 4 (length (erc-buffer-list))))
-      (should (equal (list (get-buffer "#chan@barnet")
-                           (get-buffer "#chan@foonet"))
-                     (erc-scenarios-common-buflist "#chan"))))
-
-    (ert-info ("#chan@foonet is exclusive, no cross-contamination")
-      (with-current-buffer "#chan@foonet"
-        (erc-d-t-search-for 1 "<bob>")
-        (erc-d-t-absent-for 0.1 "<joe>")
-        (should (eq erc-server-process erc-server-process-foo))))
-
-    (ert-info ("#chan@barnet is exclusive, no cross-contamination")
-      (with-current-buffer "#chan@barnet"
-        (erc-d-t-search-for 1 "<joe>")
-        (erc-d-t-absent-for 0.1 "<bob>")
-        (should (eq erc-server-process erc-server-process-bar))))
-
-    (ert-info ("All output sent")
-      (with-current-buffer "#chan@foonet"
-        (while (accept-process-output erc-server-process-foo))
-        (erc-d-t-search-for 3 "please your lordship"))
-      (with-current-buffer "#chan@barnet"
-        (while (accept-process-output erc-server-process-bar))
-        (erc-d-t-search-for 3 "I'll bid adieu")))))
-
-(cl-defun erc-scenarios-common--base-network-id-bouncer
-    ((&key autop foo-id bar-id after
-           &aux
-           (foo-id (and foo-id 'oofnet))
-           (bar-id (and bar-id 'rabnet))
-           (serv-buf-foo (if foo-id "oofnet" "foonet"))
-           (serv-buf-bar (if bar-id "rabnet" "barnet"))
-           (chan-buf-foo (if foo-id "#chan@oofnet" "#chan@foonet"))
-           (chan-buf-bar (if bar-id "#chan@rabnet" "#chan@barnet")))
-     &rest dialogs)
-  "Ensure retired option `erc-rename-buffers' is now the default behavior.
-The option `erc-rename-buffers' is now deprecated and on by default, so
-this now just asserts baseline behavior.  Originally from scenario
-clash-of-chans/rename-buffers as explained in Bug#48598: 28.0.50;
-buffer-naming collisions involving bouncers in ERC."
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/network-id/bouncer")
-       (erc-d-t-cleanup-sleep-secs 1)
-       (erc-server-flood-penalty 0.1)
-       (dumb-server (apply #'erc-d-run "localhost" t dialogs))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-auto-reconnect autop)
-       erc-server-buffer-foo erc-server-process-foo
-       erc-server-buffer-bar erc-server-process-bar)
-
-    (ert-info ("Connect to foonet")
-      (with-current-buffer
-          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "foonet:changeme"
-                                           :full-name "tester"
-                                           :id foo-id))
-        (setq erc-server-process-foo erc-server-process)
-        (erc-scenarios-common-assert-initial-buf-name foo-id port)
-        (erc-d-t-wait-for 3 (eq (erc-network) 'foonet))
-        (erc-d-t-wait-for 3 (string= (buffer-name) serv-buf-foo))
-        (funcall expect 5 "foonet")))
-
-    (ert-info ("Join #chan@foonet")
-      (with-current-buffer erc-server-buffer-foo (erc-cmd-JOIN "#chan"))
-      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
-        (funcall expect 5 "<alice>")))
-
-    (ert-info ("Connect to barnet")
-      (with-current-buffer
-          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "barnet:changeme"
-                                           :full-name "tester"
-                                           :id bar-id))
-        (setq erc-server-process-bar erc-server-process)
-        (erc-scenarios-common-assert-initial-buf-name bar-id port)
-        (erc-d-t-wait-for 3 (eq (erc-network) 'barnet))
-        (erc-d-t-wait-for 3 (string= (buffer-name) serv-buf-bar))
-        (funcall expect 5 "barnet")))
-
-    (ert-info ("Server buffers are unique, no names based on IPs")
-      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
-      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
-
-    (ert-info ("Join #chan@barnet")
-      (with-current-buffer erc-server-buffer-bar (erc-cmd-JOIN "#chan")))
-
-    (erc-d-t-wait-for 5 "Exactly 2 #chan-prefixed buffers exist"
-      (equal (list (get-buffer chan-buf-bar)
-                   (get-buffer chan-buf-foo))
-             (erc-scenarios-common-buflist "#chan")))
-
-    (ert-info ("#chan@<esid> is exclusive to foonet")
-      (with-current-buffer chan-buf-foo
-        (erc-d-t-search-for 1 "<bob>")
-        (erc-d-t-absent-for 0.1 "<joe>")
-        (should (eq erc-server-process erc-server-process-foo))
-        (while (accept-process-output erc-server-process-foo))
-        (erc-d-t-search-for 1 "ape is dead")
-        (should-not (erc-server-process-alive))))
-
-    (ert-info ("#chan@<esid> is exclusive to barnet")
-      (with-current-buffer chan-buf-bar
-        (erc-d-t-search-for 1 "<joe>")
-        (erc-d-t-absent-for 0.1 "<bob>")
-        (should (eq erc-server-process erc-server-process-bar))
-        (while (accept-process-output erc-server-process-bar))
-        (erc-d-t-search-for 1 "keeps you from dishonour")
-        (should-not (erc-server-process-alive))))
-
-    (when after (funcall after))))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--base ()
-  (erc-scenarios-common--base-network-id-bouncer () 'foonet 'barnet))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--id-foo ()
-  (erc-scenarios-common--base-network-id-bouncer '(:foo-id t) 'foonet 'barnet))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--id-bar ()
-  (erc-scenarios-common--base-network-id-bouncer '(:bar-id t) 'foonet 'barnet))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--both ()
-  (erc-scenarios-common--base-network-id-bouncer '(:foo-id t :bar-id t)
-                                                 'foonet 'barnet))
-
-(defun erc-scenarios--clash-rename-pass-handler (dialog exchange)
-  (when (eq (erc-d-dialog-name dialog) 'stub-again)
-    (let* ((match (erc-d-exchange-match exchange 1))
-           (sym (if (string= match "foonet") 'foonet-again 'barnet-again)))
-      (should (member match (list "foonet" "barnet")))
-      (erc-d-load-replacement-dialog dialog sym 1))))
-
-(defun erc-scenarios-common--base-network-id-bouncer--reconnect (foo-id bar-id)
-  (let ((erc-d-spec-vars '((token . (group (| "barnet" "foonet")))))
-        (erc-d-match-handlers
-         ;; Auto reconnect is nondeterministic, so let computer decide
-         (list :pass #'erc-scenarios--clash-rename-pass-handler))
-        (after
-         (lambda ()
-           ;; Simulate disconnection and `erc-server-auto-reconnect'
-           (ert-info ("Reconnect to foonet and barnet back-to-back")
-             (with-current-buffer (if foo-id "oofnet" "foonet")
-               (erc-d-t-wait-for 5 (erc-server-process-alive)))
-             (with-current-buffer (if bar-id "rabnet" "barnet")
-               (erc-d-t-wait-for 5 (erc-server-process-alive))))
-
-           (ert-info ("#chan@foonet is exclusive to foonet")
-             (with-current-buffer (if foo-id "#chan@oofnet" "#chan@foonet")
-               (erc-d-t-search-for 1 "<alice>")
-               (erc-d-t-absent-for 0.1 "<joe>")
-               (while (accept-process-output erc-server-process))
-               (erc-d-t-search-for 3 "please your lordship")))
-
-           (ert-info ("#chan@barnet is exclusive to barnet")
-             (with-current-buffer (if bar-id "#chan@rabnet" "#chan@barnet")
-               (erc-d-t-search-for 1 "<joe>")
-               (erc-d-t-absent-for 0.1 "<bob>")
-               (while (accept-process-output erc-server-process))
-               (erc-d-t-search-for 1 "much in private")))
-
-           ;; XXX this is important (reconnects overlapped, so we'd get
-           ;; chan@127.0.0.1:6667)
-           (should-not (erc-scenarios-common-buflist "127.0.0.1"))
-           ;; Reconnection order doesn't matter here because session objects
-           ;; are persisted, meaning original timestamps preserved.
-           (should (equal (list (get-buffer (if bar-id "#chan@rabnet"
-                                              "#chan@barnet"))
-                                (get-buffer (if foo-id "#chan@oofnet"
-                                              "#chan@foonet")))
-                          (erc-scenarios-common-buflist "#chan"))))))
-    (erc-scenarios-common--base-network-id-bouncer
-     (list :autop t :foo-id foo-id :bar-id bar-id :after after)
-     'foonet-drop 'barnet-drop
-     'stub-again 'stub-again
-     'foonet-again 'barnet-again)))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--reconnect-base ()
-  (erc-scenarios-common--base-network-id-bouncer--reconnect nil nil))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--reconnect-id-foo ()
-  (erc-scenarios-common--base-network-id-bouncer--reconnect 'foo-id nil))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--reconnect-id-bar ()
-  (erc-scenarios-common--base-network-id-bouncer--reconnect nil 'bar-id))
-
-(ert-deftest erc-scenarios-base-network-id-bouncer--reconnect-both ()
-  (erc-scenarios-common--base-network-id-bouncer--reconnect 'foo-id 'bar-id))
-
-;; Ensure deprecated option still respected when old default value
-;; explicitly set ("respected" in the sense of having names reflect
-;; dialed TCP endpoints with possible uniquifiers but without any of
-;; the old issues, pre-bug#48598).
-
-(defun erc-scenarios-common--base-compat-no-rename-bouncer (dialogs auto more)
-  (erc-scenarios-common-with-cleanup
-      ;; These actually *are* (assigned-)network-id related because
-      ;; our kludge assigns one after the fact.
-      ((erc-scenarios-common-dialog "base/network-id/bouncer")
-       (erc-d-t-cleanup-sleep-secs 1)
-       (erc-server-flood-penalty 0.1)
-       (dumb-server (apply #'erc-d-run "localhost" t dialogs))
-       (port (process-contact dumb-server :service))
-       (chan-buf-foo (format "#chan@127.0.0.1:%d" port))
-       (chan-buf-bar (format "#chan@127.0.0.1:%d<2>" port))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-auto-reconnect auto)
-       erc-server-buffer-foo erc-server-process-foo
-       erc-server-buffer-bar erc-server-process-bar)
-
-    (ert-info ("Connect to foonet")
-      (with-current-buffer
-          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "foonet:changeme"
-                                           :full-name "tester"
-                                           :id nil))
-        (setq erc-server-process-foo erc-server-process)
-        (erc-d-t-wait-for 3 (eq (erc-network) 'foonet))
-        (erc-d-t-wait-for 3 "Final buffer name determined"
-          (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (funcall expect 5 "foonet")))
-
-    (ert-info ("Join #chan@foonet")
-      (with-current-buffer erc-server-buffer-foo (erc-cmd-JOIN "#chan"))
-      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
-        (funcall expect 5 "<alice>")))
-
-    (ert-info ("Connect to barnet")
-      (with-current-buffer
-          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
-                                           :port port
-                                           :nick "tester"
-                                           :password "barnet:changeme"
-                                           :full-name "tester"
-                                           :id nil))
-        (setq erc-server-process-bar erc-server-process)
-        (erc-d-t-wait-for 3 (eq (erc-network) 'barnet))
-        (erc-d-t-wait-for 3 "Final buffer name determined"
-          (string= (buffer-name) (format "127.0.0.1:%d<2>" port)))
-        (funcall expect 5 "barnet")))
-
-    (ert-info ("Server buffers are unique, no names based on IPs")
-      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
-      (should (equal (erc-scenarios-common-buflist "127.0.0.1")
-                     (list (get-buffer (format "127.0.0.1:%d<2>" port))
-                           (get-buffer (format "127.0.0.1:%d" port))))))
-
-    (ert-info ("Join #chan@barnet")
-      (with-current-buffer erc-server-buffer-bar (erc-cmd-JOIN "#chan")))
-
-    (erc-d-t-wait-for 5 "Exactly 2 #chan-prefixed buffers exist"
-      (equal (list (get-buffer chan-buf-bar)
-                   (get-buffer chan-buf-foo))
-             (erc-scenarios-common-buflist "#chan")))
-
-    (ert-info ("#chan@127.0.0.1:$port is exclusive to foonet")
-      (with-current-buffer chan-buf-foo
-        (erc-d-t-search-for 1 "<bob>")
-        (erc-d-t-absent-for 0.1 "<joe>")
-        (should (eq erc-server-process erc-server-process-foo))
-        (while (accept-process-output erc-server-process-foo))
-        (erc-d-t-search-for 1 "ape is dead")
-        (should-not (erc-server-process-alive))))
-
-    (ert-info ("#chan@127.0.0.1:$port<2> is exclusive to barnet")
-      (with-current-buffer chan-buf-bar
-        (erc-d-t-search-for 1 "<joe>")
-        (erc-d-t-absent-for 0.1 "<bob>")
-        (should (eq erc-server-process erc-server-process-bar))
-        (while (accept-process-output erc-server-process-bar))
-        (erc-d-t-search-for 1 "keeps you from dishonour")
-        (should-not (erc-server-process-alive))))
-
-    (when more (funcall more))))
-
-(ert-deftest erc-scenarios-base-compat-no-rename-bouncer--basic ()
-  (with-suppressed-warnings ((obsolete erc-rename-buffers))
-    (let (erc-rename-buffers)
-      (erc-scenarios-common--base-compat-no-rename-bouncer
-       '(foonet barnet) nil nil))))
-
-(ert-deftest erc-scenarios-base-compat-no-rename-bouncer--reconnect ()
-  (let ((erc-d-spec-vars '((token . (group (| "barnet" "foonet")))))
-        (erc-d-match-handlers
-         (list :pass #'erc-scenarios--clash-rename-pass-handler))
-        (dialogs '(foonet-drop barnet-drop stub-again stub-again
-                               foonet-again barnet-again))
-        (after
-         (lambda ()
-           (pcase-let* ((`(,barnet ,foonet)
-                         (erc-scenarios-common-buflist "127.0.0.1"))
-                        (port (process-contact (with-current-buffer foonet
-                                                 erc-server-process)
-                                               :service)))
-
-             (ert-info ("Sanity check: barnet retains uniquifying suffix")
-               (should (string-suffix-p "<2>" (buffer-name barnet))))
-
-             ;; Simulate disconnection and `erc-server-auto-reconnect'
-             (ert-info ("Reconnect to foonet and barnet back-to-back")
-               (with-current-buffer foonet
-                 (erc-d-t-wait-for 5 (erc-server-process-alive)))
-               (with-current-buffer barnet
-                 (erc-d-t-wait-for 5 (erc-server-process-alive))))
-
-             (ert-info ("#chan@127.0.0.1:<port> is exclusive to foonet")
-               (with-current-buffer  (format "#chan@127.0.0.1:%d" port)
-                 (erc-d-t-search-for 1 "<alice>")
-                 (erc-d-t-absent-for 0.1 "<joe>")
-                 (while (accept-process-output erc-server-process))
-                 (erc-d-t-search-for 3 "please your lordship")))
-
-             (ert-info ("#chan@barnet is exclusive to barnet")
-               (with-current-buffer  (format "#chan@127.0.0.1:%d<2>" port)
-                 (erc-d-t-search-for 1 "<joe>")
-                 (erc-d-t-absent-for 0.1 "<bob>")
-                 (while (accept-process-output erc-server-process))
-                 (erc-d-t-search-for 1 "much in private")))
-
-             ;; Ordering deterministic here even though not so for reconnect
-             (should (equal (list barnet foonet)
-                            (erc-scenarios-common-buflist "127.0.0.1")))
-             (should (equal (list
-                             (get-buffer (format "#chan@127.0.0.1:%d<2>" port))
-                             (get-buffer (format "#chan@127.0.0.1:%d" port)))
-                            (erc-scenarios-common-buflist "#chan")))))))
-
-    (with-suppressed-warnings ((obsolete erc-rename-buffers))
-      (let (erc-rename-buffers)
-        (erc-scenarios-common--base-compat-no-rename-bouncer dialogs
-                                                             'auto after)))))
-
-;; The added complexity of a request handler definitely stinks. But on
-;; some machines, the ordering from the selector is nondeterministic,
-;; whereas normally, the filter for the last process created (in the
-;; code) gets all the initial attention. FIXME delete obsolete comment
-
-(defun erc-scenarios--rebuffed-gapless-pass-handler (dialog exchange)
-  (when (eq (erc-d-dialog-name dialog) 'pass-stub)
-    (let* ((match (erc-d-exchange-match exchange 1))
-           (sym (if (string= match "foonet") 'foonet 'barnet)))
-      (should (member match (list "foonet" "barnet")))
-      (erc-d-load-replacement-dialog dialog sym 1))))
-
-(ert-deftest erc-scenarios-base-gapless-connect ()
-  "Back-to-back entry-point invocations happen successfully.
-Originally from scenario rebuffed/gapless as explained in Bug#48598:
-28.0.50; buffer-naming collisions involving bouncers in ERC."
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/gapless-connect")
-       (erc-server-flood-penalty 0.1)
-       (erc-d-linger-secs 4)
-       (erc-server-flood-penalty erc-server-flood-penalty)
-       (erc-d-spec-vars '((token . (group (| "barnet" "foonet")))))
-       (erc-d-match-handlers
-        (list :pass #'erc-scenarios--rebuffed-gapless-pass-handler))
-       (dumb-server (erc-d-run "localhost" t
-                               'pass-stub 'pass-stub 'barnet 'foonet))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo
-       erc-server-buffer-bar)
-
-    (ert-info ("Connect twice to same endpoint without pausing")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "foonet:changeme"
-                                       :full-name "tester")
-            erc-server-buffer-bar (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "barnet:changeme"
-                                       :full-name "tester")))
-
-    (ert-info ("Returned server buffers are unique")
-      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar)))
-
-    (ert-info ("Both connections still alive")
-      (should (get-process (format "erc-127.0.0.1-%d" port)))
-      (should (get-process (format "erc-127.0.0.1-%d<1>" port))))
-
-    (with-current-buffer erc-server-buffer-bar
-      (funcall expect 2 "marked as being away"))
-
-    (with-current-buffer (erc-d-t-wait-for 20 (get-buffer "#bar"))
-      (while (accept-process-output erc-server-process))
-      (funcall expect 2 "was created on")
-      (funcall expect 2 "his second fit"))
-
-    (with-current-buffer (erc-d-t-wait-for 20 (get-buffer "#foo"))
-      (while (accept-process-output erc-server-process))
-      (funcall expect 2 "was created on")
-      (funcall expect 2 "no use of him"))))
-
-(defun erc-scenarios-common--base-reuse-buffers-server-buffers (&optional more)
-  "Show that `erc-reuse-buffers' doesn't affect server buffers.
-Overlaps some with `clash-of-chans/uniquify'.  Adapted from
-rebuffed/reuseless, described in Bug#48598: 28.0.50; buffer-naming
-collisions involving bouncers in ERC.  Run EXTRA."
-  (erc-scenarios-common-with-cleanup
-      ((erc-d-linger-secs 1)
-       (dumb-server (erc-d-run "localhost" t 'foonet 'barnet))
-       (port (process-contact dumb-server :service))
-       erc-autojoin-channels-alist)
-
-    (ert-info ("Connect to foonet")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :password "foonet:changeme"
-                                :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (erc-d-t-search-for 2 "marked as being away")))
-
-    (ert-info ("Connect to barnet")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :password "barnet:changeme"
-                                :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (erc-d-t-search-for 2 "marked as being away")))
-
-    (erc-d-t-wait-for 2 (get-buffer "foonet"))
-    (erc-d-t-wait-for 2 (get-buffer "barnet"))
-
-    (ert-info ("Server buffers are unique, no IP-based names")
-      (should-not (eq (get-buffer "foonet") (get-buffer "barnet")))
-      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
-
-    (when more (funcall more))))
-
-(ert-deftest erc-scenarios-base-reuse-buffers-server-buffers--enabled ()
-  (should erc-reuse-buffers)
-  (let ((erc-scenarios-common-dialog "base/reuse-buffers/server-buffers"))
-    (erc-scenarios-common--base-reuse-buffers-server-buffers)))
-
-(ert-deftest erc-scenarios-base-reuse-buffers-server-buffers--disabled ()
-  (should erc-reuse-buffers)
-  (let ((erc-scenarios-common-dialog "base/reuse-buffers/server-buffers")
-        erc-reuse-buffers)
-    (erc-scenarios-common--base-reuse-buffers-server-buffers)))
-
-;; This also asserts that `erc-cmd-JOIN' is no longer susceptible to a
-;; regression introduced in 28.1 (ERC 5.4) that caused phantom target
-;; buffers of the form target/server to be created via
-;; `switch-to-buffer' ("phantom" because they would go unused").  This
-;; would happen (in place of a JOIN being sent out) when a previously
-;; used (parted) target buffer existed and `erc-reuse-buffers' was
-;; nil.
-;;
-;; Note: All the `erc-get-channel-user' calls have to do with the fact
-;; that `erc-default-target' relies on the less-than-well-defined
-;; `erc-default-recipients' and is thus overloaded in the sense of
-;; being used both for retrieving a target name and checking if
-;; channel has been PARTed.  While not ideal, `erc-get-channel-user'
-;; can (also) be used to detect the latter.
-
-(defun erc-scenarios-common--base-reuse-buffers-channel-buffers ()
-  "The option `erc-reuse-buffers' is still respected when nil.
-Adapted from scenario clash-of-chans/uniquify described in Bug#48598:
-28.0.50; buffer-naming collisions involving bouncers in ERC."
-  (let ((expect (erc-d-t-make-expecter))
-        (server-process-bar (with-current-buffer "barnet" erc-server-process))
-        (server-process-foo (with-current-buffer "foonet" erc-server-process)))
-
-    (ert-info ("Unique #chan buffers exist")
-      (let ((chan-bufs (erc-scenarios-common-buflist "#chan"))
-            (names '("#chan@barnet" "#chan@foonet")))
-        (should (member (buffer-name (pop chan-bufs)) names))
-        (should (member (buffer-name (pop chan-bufs)) names))
-        (should-not chan-bufs)))
-
-    (ert-info ("#chan@foonet is exclusive and not contaminated")
-      (with-current-buffer "#chan@foonet"
-        (funcall expect 1 "<bob>")
-        (erc-d-t-absent-for 0.1 "<joe>")
-        (funcall expect 1 "strength to climb")
-        (should (eq erc-server-process server-process-foo))))
-
-    (ert-info ("#chan@barnet is exclusive and not contaminated")
-      (with-current-buffer "#chan@barnet"
-        (funcall expect 1 "<joe>")
-        (erc-d-t-absent-for 0.1 "<bob>")
-        (funcall expect 1 "the loudest noise")
-        (should (eq erc-server-process server-process-bar))))
-
-    (ert-info ("Part #chan@foonet")
-      (with-current-buffer "#chan@foonet"
-        (erc-d-t-search-for 1 "shake my sword")
-        (erc-cmd-PART "#chan")
-        (funcall expect 3 "You have left channel #chan")
-        (erc-cmd-JOIN "#chan")))
-
-    (ert-info ("Part #chan@barnet")
-      (with-current-buffer "#chan@barnet"
-        (funcall expect 3 "Arm it in rags")
-        (should (erc-get-channel-user (erc-current-nick)))
-        (erc-cmd-PART "#chan")
-        (funcall expect 3 "You have left channel #chan")
-        (should-not (erc-get-channel-user (erc-current-nick)))
-        (erc-cmd-JOIN "#chan")))
-
-    (erc-d-t-wait-for 3 "New unique target buffer for #chan@foonet created"
-      (get-buffer "#chan@foonet<2>"))
-
-    (ert-info ("Activity continues in new, <n>-suffixed #chan@foonet buffer")
-      (with-current-buffer "#chan@foonet"
-        (should-not (erc-get-channel-user (erc-current-nick))))
-      (with-current-buffer "#chan@foonet<2>"
-        (should (erc-get-channel-user (erc-current-nick)))
-        (funcall expect 2 "You have joined channel #chan")
-        (funcall expect 2 "#chan was created on")
-        (funcall expect 2 "<alice>")
-        (should (eq erc-server-process server-process-foo))
-        (erc-d-t-absent-for 0.2 "<joe>")))
-
-    (erc-d-t-wait-for 3 "New unique target buffer for #chan@barnet created"
-      (get-buffer "#chan@barnet<2>"))
-
-    (ert-info ("Activity continues in new, <n>-suffixed #chan@barnet buffer")
-      (with-current-buffer "#chan@barnet"
-        (should-not (erc-get-channel-user (erc-current-nick))))
-      (with-current-buffer "#chan@barnet<2>"
-        (funcall expect 2 "You have joined channel #chan")
-        (funcall expect 1 "Users on #chan: @mike joe tester")
-        (funcall expect 2 "<mike>")
-        (should (eq erc-server-process server-process-bar))
-        (erc-d-t-absent-for 0.2 "<bob>")))
-
-    (ert-info ("Two new chans created for a total of four")
-      (let* ((bufs (erc-scenarios-common-buflist "#chan"))
-             (names (sort (mapcar #'buffer-name bufs) #'string<)))
-        (should (equal names '("#chan@barnet" "#chan@barnet<2>"
-                               "#chan@foonet" "#chan@foonet<2>")))))
-
-    (ert-info ("All output sent")
-      (with-current-buffer "#chan@foonet<2>"
-        (while (accept-process-output server-process-foo))
-        (funcall expect 3 "most lively"))
-      (with-current-buffer "#chan@barnet<2>"
-        (while (accept-process-output server-process-bar))
-        (funcall expect 3 "soul black")))))
-
-(ert-deftest erc-scenarios-base-reuse-buffers-channel-buffers--disabled ()
-  (should erc-reuse-buffers)
-  (let ((erc-scenarios-common-dialog "base/reuse-buffers/channel-buffers")
-        (erc-server-flood-penalty 0.1)
-        erc-reuse-buffers)
-    (erc-scenarios-common--base-reuse-buffers-server-buffers
-     #'erc-scenarios-common--base-reuse-buffers-channel-buffers)))
-
-;; The server changes your nick just after registration.
-
-(ert-deftest erc-scenarios-base-renick-self-auto ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/renick/self")
-       (erc-d-linger-secs 0.1)
-       (erc-server-flood-penalty 0.1)
-       (dumb-server (erc-d-run "localhost" t 'auto))
-       (port (process-contact dumb-server :service))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "foonet:changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "foonet"))
-      (erc-d-t-search-for 10 "Your new nickname is dummy"))
-
-    (ert-info ("Joined by bouncer to #foo, own nick present")
-      (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
-        (erc-d-t-search-for 10 "dummy")
-        (erc-d-t-search-for 10 "On Thursday")))))
-
-;; You change your nickname manually in a server buffer; a message is
-;; printed in channel buffers.
-
-(ert-deftest erc-scenarios-base-renick-self-manual ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/renick/self")
-       (erc-d-linger-secs 0.1)
-       (erc-server-flood-penalty 0.1)
-       (dumb-server (erc-d-run "localhost" t 'manual))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "foonet:changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (erc-d-t-wait-for 3 (get-buffer "foonet"))
-
-    (ert-info ("Joined by bouncer to #foo, own nick present")
-      (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
-        (funcall expect 5 "tester")
-        (funcall expect 5 "On Thursday")
-        (erc-with-server-buffer (erc-cmd-NICK "dummy"))
-        (funcall expect 5 "Your new nickname is dummy")
-        (funcall expect 5 "<bob> dummy: Hi")
-        ;; Regression in which changing a nick would trigger #foo@foonet
-        (erc-d-t-ensure-for 0.4 (equal (buffer-name) "#foo"))))))
-
-;; You connect to the same network with two different nicks.  You
-;; manually change the first nick at some point, and buffer names are
-;; updated correctly.
-
-(ert-deftest erc-scenarios-base-renick-self-qualified ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/renick/self")
-       (dumb-server (erc-d-run "localhost" t 'qual-tester 'qual-chester))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.1)
-       (erc-server-flood-margin 30)
-       erc-serv-buf-a erc-serv-buf-b)
-
-    (ert-info ("Connect to foonet with nick tester")
-      (with-current-buffer
-          (setq erc-serv-buf-a (erc :server "127.0.0.1"
-                                    :port port
-                                    :nick "tester"
-                                    :password "changeme"
-                                    :full-name "tester"))
-        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
-
-    (ert-info ("Connect to foonet with nick chester")
-      (with-current-buffer
-          (setq erc-serv-buf-b (erc :server "127.0.0.1"
-                                    :port port
-                                    :nick "chester"
-                                    :password "changeme"
-                                    :full-name "chester"))))
-
-    (erc-d-t-wait-for 3 "Dialed Buflist is Empty"
-      (not (erc-scenarios-common-buflist "127.0.0.1")))
-
-    (with-current-buffer  "foonet/tester"
-      (funcall expect 3 "debug mode")
-      (erc-cmd-JOIN "#chan"))
-
-    (with-current-buffer  "foonet/chester"
-      (funcall expect 3 "debug mode")
-      (erc-cmd-JOIN "#chan"))
-
-    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/tester"))
-    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/chester"))
-
-    (ert-info ("Greets other nick in same channel")
-      (with-current-buffer "#chan@foonet/tester"
-        (funcall expect 5 "<bob> chester, welcome!")
-        (erc-cmd-NICK "dummy")
-        (funcall expect 5 "Your new nickname is dummy")
-        (funcall expect 5 "find the forester")
-        (erc-d-t-wait-for 5 (string= (buffer-name) "#chan@foonet/dummy"))))
-
-    (ert-info ("Renick propagated throughout all buffers of process")
-      (should-not (get-buffer "#chan@foonet/tester"))
-      (should-not (get-buffer "foonet/tester"))
-      (should (get-buffer "foonet/dummy")))))
-
-;; When a channel user changes their nick, any query buffers for them
-;; are updated.
-
-(ert-deftest erc-scenarios-base-renick-queries-solo ()
-
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/renick/queries")
-       (erc-d-linger-secs 0.1)
-       (erc-server-flood-penalty 0.1)
-       (erc-server-flood-margin 20)
-       (dumb-server (erc-d-run "localhost" t 'solo))
-       (port (process-contact dumb-server :service))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "foonet:changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (erc-d-t-wait-for 1 (get-buffer "foonet"))
-
-    (ert-info ("Joined by bouncer to #foo, pal persent")
-      (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
-        (erc-d-t-search-for 1 "On Thursday")
-        (goto-char erc-input-marker)
-        (insert "hi")
-        (erc-send-current-line)))
-
-    (erc-d-t-wait-for 10 "Query buffer appears with message from pal"
-      (get-buffer "Lal"))
-
-    (ert-info ("Chat with pal, who changes name")
-      (with-current-buffer "Lal"
-        (erc-d-t-search-for 3 "hello")
-        (goto-char erc-input-marker)
-        (insert "hi")
-        (erc-send-current-line)
-        (erc-d-t-search-for 10 "is now known as Linguo")
-        (should-not (search-forward "is now known as Linguo" nil t))))
-
-    (erc-d-t-wait-for 1 (get-buffer "Linguo"))
-    (should-not (get-buffer "Lal"))
-
-    (with-current-buffer "Linguo"
-      (goto-char erc-input-marker)
-      (insert "howdy Linguo")
-      (erc-send-current-line))
-
-    (with-current-buffer "#foo"
-      (erc-d-t-search-for 10 "is now known as Linguo")
-      (should-not (search-forward "is now known as Linguo" nil t))
-      (erc-cmd-PART ""))
-
-    (with-current-buffer "Linguo"
-      (erc-d-t-search-for 10 "get along"))))
-
-;; You share a channel and a query buffer with a user on two different
-;; networks (through a proxy).  The user changes their nick on both
-;; networks at the same time.  Query buffers are updated accordingly.
-
-(ert-deftest erc-scenarios-base-renick-queries-bouncer ()
-
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/renick/queries")
-       (erc-d-linger-secs 0.5)
-       (erc-server-flood-penalty 0.1)
-       (erc-server-flood-margin 30)
-       (dumb-server (erc-d-run "localhost" t 'bouncer-foonet 'bouncer-barnet))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       erc-accidental-paste-threshold-seconds
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo
-       erc-server-buffer-bar)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "foonet:changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (erc-d-t-wait-for 1 (get-buffer "foonet"))
-
-    (ert-info ("Connect to barnet")
-      (setq erc-server-buffer-bar (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "barnet:changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-bar
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (erc-d-t-wait-for 1 (get-buffer "barnet"))
-    (should-not (erc-scenarios-common-buflist "127.0.0.1"))
-
-    (ert-info ("Joined by bouncer to #chan@foonet, pal persent")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan@foonet"))
-        (funcall expect 1 "rando")
-        (funcall expect 1 "simply misused")))
-
-    (ert-info ("Joined by bouncer to #chan@barnet, pal persent")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan@barnet"))
-        (funcall expect 1 "rando")
-        (funcall expect 1 "come, sir, I am")))
-
-    (ert-info ("Query buffer exists for rando@foonet")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "rando@foonet"))
-        (funcall expect 1 "guess not")
-        (goto-char erc-input-marker)
-        (insert "I here")
-        (erc-send-current-line)))
-
-    (ert-info ("Query buffer exists for rando@barnet")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "rando@barnet"))
-        (funcall expect 2 "rentacop")
-        (goto-char erc-input-marker)
-        (insert "Linda said you were gonna kill me.")
-        (erc-send-current-line)))
-
-    (ert-info ("Sync convo for rando@foonet")
-      (with-current-buffer "rando@foonet"
-        (funcall expect 1 "u are dumb")
-        (goto-char erc-input-marker)
-        (insert "not so")
-        (erc-send-current-line)))
-
-    (ert-info ("Sync convo for rando@barnet")
-      (with-current-buffer "rando@barnet"
-        (funcall expect 3 "I never saw her before")
-        (goto-char erc-input-marker)
-        (insert "You aren't with Wage?")
-        (erc-send-current-line)))
-
-    (erc-d-t-wait-for 1 (get-buffer "frenemy@foonet"))
-    (erc-d-t-wait-for 1 (get-buffer "frenemy@barnet"))
-    (should-not (get-buffer "rando@foonet"))
-    (should-not (get-buffer "rando@barnet"))
-
-    (with-current-buffer "frenemy@foonet"
-      (funcall expect 1 "now known as")
-      (funcall expect 1 "doubly so"))
-
-    (with-current-buffer "frenemy@barnet"
-      (funcall expect 1 "now known as")
-      (funcall expect 1 "reality picture"))
-
-    (when noninteractive
-      (with-current-buffer "frenemy@barnet" (kill-buffer))
-      (erc-d-t-wait-for 2 (get-buffer "frenemy"))
-      (should-not (get-buffer "frenemy@foonet")))
-
-    (with-current-buffer "#chan@foonet"
-      (funcall expect 10 "is now known as frenemy")
-      (should-not (search-forward "now known as frenemy" nil t)) ; regression
-      (funcall expect 10 "words are razors"))
-
-    (with-current-buffer "#chan@barnet"
-      (funcall expect 10 "is now known as frenemy")
-      (should-not (search-forward "now known as frenemy" nil t))
-      (while (accept-process-output erc-server-process))
-      (funcall expect 10 "I have lost"))))
-
-(ert-deftest erc-scenarios-aux-unix-socket ()
-  (skip-unless (featurep 'make-network-process '(:family local)))
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/renick/self")
-       (erc-d-linger-secs 0.1)
-       (erc-server-flood-penalty 0.1)
-       (sock (expand-file-name "erc-d.sock" temporary-file-directory))
-       (erc-scenarios-common-extra-teardown (lambda ()
-                                              (delete-file sock)))
-       (erc-server-connect-function
-        (lambda (n b _ p &rest r)
-          (apply #'make-network-process
-                 `(:name ,n :buffer ,b :service ,p :family local ,@r))))
-       (dumb-server (erc-d-run nil sock 'auto))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "fake"
-                                       :port sock
-                                       :nick "tester"
-                                       :password "foonet:changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "fake:%s" sock)))))
-
-    (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "foonet"))
-      (erc-d-t-search-for 10 "Your new nickname is dummy"))
-
-    (ert-info ("Joined by bouncer to #foo, own nick present")
-      (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
-        (erc-d-t-search-for 10 "dummy")
-        (erc-d-t-search-for 10 "On Thursday")))))
-
-;; See `erc-update-server-buffer-name'.  A perceived loss in
-;; network connectivity turns out to be a false alarm, but the
-;; bouncer has already accepted the second connection
-
-(defun erc-scenarios--base-aborted-reconnect ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/reconnect")
-       (erc-d-t-cleanup-sleep-secs 1)
-       (erc-d-linger-secs 0.5)
-       (dumb-server (erc-d-run "localhost" t 'aborted 'aborted-dupe))
-       (port (process-contact dumb-server :service))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (ert-info ("Server buffer is unique and temp name is absent")
-      (erc-d-t-wait-for 1 (get-buffer "FooNet"))
-      (should-not (erc-scenarios-common-buflist "127.0.0.1"))
-      (with-current-buffer erc-server-buffer-foo
-        (erc-cmd-JOIN "#chan")))
-
-    (ert-info ("Channel buffer #chan alive and well")
-      (with-current-buffer (erc-d-t-wait-for 4 (get-buffer "#chan"))
-        (erc-d-t-search-for 10 "welcome")))
-
-    (ert-info ("Connect to foonet again")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "changeme"
-                                       :full-name "tester"))
-      (let ((inhibit-message noninteractive))
-        (with-current-buffer erc-server-buffer-foo
-          (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-          (erc-d-t-wait-for 5 (not (erc-server-process-alive)))
-          (erc-d-t-search-for 10 "FooNet still connected"))))
-
-    (ert-info ("Server buffer is unique and temp name is absent")
-      (should (equal (list (get-buffer "FooNet"))
-                     (erc-scenarios-common-buflist "FooNet")))
-      (should (equal (list (get-buffer (format "127.0.0.1:%d" port)))
-                     (erc-scenarios-common-buflist "127.0.0.1"))))
-
-    (ert-info ("Channel buffer #chan still going")
-      (with-current-buffer "#chan"
-        (erc-d-t-search-for 10 "and be prosperous")))))
-
-(ert-deftest erc-scenarios-base-aborted-reconnect ()
-  :tags '(:unstable)
-  (let ((tries 3)
-        (timeout 1)
-        failed)
-    (while (condition-case _err
-               (progn
-                 (erc-scenarios--base-aborted-reconnect)
-                 nil)
-             (ert-test-failed
-              (message "Test %S failed; %s attempt(s) remaining."
-                       (ert-test-name (ert-running-test))
-                       tries)
-              (sleep-for (cl-incf timeout))
-              (not (setq failed (zerop (cl-decf tries)))))))
-    (should-not failed)))
-
-;; This defends against a regression in `erc-server-PRIVMSG' caused by
-;; the removal of `erc-auto-query'.  When an active channel buffer is
-;; killed off and PRIVMSGs arrive targeting it, the buffer should be
-;; recreated.  See elsewhere for NOTICE logic, which is more complex.
-
-(ert-deftest erc-scenarios-base-channel-buffer-revival ()
-
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/channel-buffer-revival")
-       (erc-d-linger-secs 0.5)
-       (dumb-server (erc-d-run "localhost" t 'foonet))
-       (port (process-contact dumb-server :service))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (ert-info ("Server buffer is unique and temp name is absent")
-      (erc-d-t-wait-for 1 (get-buffer "FooNet"))
-      (should-not (erc-scenarios-common-buflist "127.0.0.1"))
-      (with-current-buffer erc-server-buffer-foo
-        (erc-cmd-JOIN "#chan")))
-
-    (ert-info ("Channel buffer #chan alive and well")
-      (with-current-buffer (erc-d-t-wait-for 8 (get-buffer "#chan"))
-        (erc-d-t-search-for 10 "Our queen and all her elves")
-        (kill-buffer)))
-
-    (should-not (get-buffer "#chan"))
-
-    (ert-info ("Channel buffer #chan revived")
-      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
-        (erc-d-t-search-for 10 "and be prosperous")))))
-
-;; This ensures we only reconnect `erc-server-reconnect-attempts'
-;; (rather than infinitely many) times, which can easily happen when
-;; tweaking code related to process sentinels in erc-backend.el.
-
-(ert-deftest erc-scenarios-base-reconnect-timer ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/reconnect")
-       (dumb-server (erc-d-run "localhost" t 'timer 'timer 'timer-last))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-auto-reconnect t)
-       erc-autojoin-channels-alist
-       erc-server-buffer)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer (erc :server "127.0.0.1"
-                                   :port port
-                                   :nick "tester"
-                                   :password "changeme"
-                                   :full-name "tester"))
-      (with-current-buffer erc-server-buffer
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (ert-info ("Server tries to connect thrice (including initial attempt)")
-      (with-current-buffer erc-server-buffer
-        (dotimes (n 3)
-          (ert-info ((format "Attempt %d" n))
-            (funcall expect 3 "Opening connection")
-            (funcall expect 2 "Password incorrect")
-            (funcall expect 2 "Connection failed!")
-            (funcall expect 2 "Re-establishing connection")))
-        (ert-info ("Prev attempt was final")
-          (erc-d-t-absent-for 1 "Opening connection" (point)))))
-
-    (ert-info ("Server buffer is unique and temp name is absent")
-      (should (equal (list (get-buffer (format "127.0.0.1:%d" port)))
-                     (erc-scenarios-common-buflist "127.0.0.1"))))))
-
-(defun erc-scenarios-common--base-reconnect-options (test)
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/reconnect")
-       (dumb-server (erc-d-run "localhost" t 'options 'options-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.1)
-       (erc-server-auto-reconnect t)
-       erc-autojoin-channels-alist
-       erc-server-buffer)
-
-    (should (memq 'autojoin erc-modules))
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer (erc :server "127.0.0.1"
-                                   :port port
-                                   :nick "tester"
-                                   :password "changeme"
-                                   :full-name "tester"))
-      (with-current-buffer erc-server-buffer
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (funcall expect 1 "debug mode")))
-
-    (ert-info ("Wait for some output in channels")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
-        (funcall expect 10 "welcome")))
-
-    (ert-info ("Server buffer shows connection failed")
-      (with-current-buffer erc-server-buffer
-        (funcall expect 10 "Connection failed!  Re-establishing")))
-
-    (should (equal erc-autojoin-channels-alist '((FooNet "#chan"))))
-
-    (funcall test)
-
-    (with-current-buffer "FooNet" (erc-cmd-JOIN "#spam"))
-
-    (erc-d-t-wait-for 5 "Channel #spam shown when autojoined"
-      (eq (window-buffer) (get-buffer "#spam")))
-
-    (ert-info ("Wait for auto reconnect")
-      (with-current-buffer erc-server-buffer
-        (funcall expect 10 "still in debug mode")))
-
-    (ert-info ("Wait for activity to recommence in channels")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
-        (funcall expect 10 "forest of Arden"))
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
-        (funcall expect 10 "her elves come here anon")))))
-
-(ert-deftest erc-scenarios-base-reconnect-options--default ()
-  (should (eq erc-join-buffer 'buffer))
-  (should-not erc-reconnect-display)
-
-  ;; FooNet (the server buffer) is not switched to because it's
-  ;; already current (but not shown) when `erc-open' is called.  See
-  ;; related conditional guard towards the end of that function.
-
-  (erc-scenarios-common--base-reconnect-options
-   (lambda ()
-     (pop-to-buffer-same-window "*Messages*")
-
-     (erc-d-t-ensure-for 1 "Server buffer not shown"
-       (not (eq (window-buffer) (get-buffer "FooNet"))))
-
-     (erc-d-t-wait-for 5 "Channel #chan shown when autojoined"
-       (eq (window-buffer) (get-buffer "#chan"))))))
-
-(ert-deftest erc-scenarios-base-reconnect-options--bury ()
-  (should (eq erc-join-buffer 'buffer))
-  (should-not erc-reconnect-display)
-
-  (let ((erc-reconnect-display 'bury))
-    (erc-scenarios-common--base-reconnect-options
-
-     (lambda ()
-       (pop-to-buffer-same-window "*Messages*")
-
-       (erc-d-t-ensure-for 1 "Server buffer not shown"
-         (not (eq (window-buffer) (get-buffer "FooNet"))))
-
-       (erc-d-t-ensure-for 3 "Channel #chan not shown"
-         (not (eq (window-buffer) (get-buffer "#chan"))))
-
-       (eq (window-buffer) (messages-buffer))))))
-
-(cl-defun erc-scenarios-common--base-network-id-same-network
-    ((&key nick id server chan
-           &aux (nick-a nick) (id-a id) (serv-buf-a server) (chan-buf-a chan))
-     (&key nick id server chan
-           &aux (nick-b nick) (id-b id) (serv-buf-b server) (chan-buf-b chan)))
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/network-id/same-network")
-       (dumb-server (erc-d-run "localhost" t 'tester 'chester))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.1)
-       (erc-server-flood-margin 30)
-       erc-serv-buf-a erc-serv-buf-b)
-
-    (ert-info ("Connect to foonet with nick tester")
-      (with-current-buffer
-          (setq erc-serv-buf-a (erc :server "127.0.0.1"
-                                    :port port
-                                    :nick nick-a
-                                    :password "changeme"
-                                    :full-name nick-a
-                                    :id id-a))
-        (erc-scenarios-common-assert-initial-buf-name id-a port)
-        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
-
-    (ert-info ("Connect to foonet with nick chester")
-      (with-current-buffer
-          (setq erc-serv-buf-b (erc :server "127.0.0.1"
-                                    :port port
-                                    :nick nick-b
-                                    :password "changeme"
-                                    :full-name nick-b
-                                    :id id-b))
-        (erc-scenarios-common-assert-initial-buf-name id-b port)))
-
-    (erc-d-t-wait-for 3 (not (erc-scenarios-common-buflist "127.0.0.1")))
-
-    (with-current-buffer erc-serv-buf-a
-      (should (string= (buffer-name) serv-buf-a))
-      (funcall expect 3 "debug mode")
-      (erc-cmd-JOIN "#chan"))
-
-    (with-current-buffer erc-serv-buf-b
-      (should (string= (buffer-name) serv-buf-b))
-      (funcall expect 3 "debug mode")
-      (erc-cmd-JOIN "#chan"))
-
-    (erc-d-t-wait-for 10 (get-buffer chan-buf-a))
-    (erc-d-t-wait-for 10 (get-buffer chan-buf-b))
-
-    (ert-info ("Greets other nick in same channel")
-      (with-current-buffer chan-buf-a
-        (funcall expect 5 "chester")
-        (funcall expect 5 "find the forester")
-        (erc-cmd-MSG "#chan chester: hi")))
-
-    (ert-info ("Sees other nick in same channel")
-      (with-current-buffer chan-buf-b
-        (funcall expect 5 "tester")
-        (funcall expect 10 "<tester> chester: hi")
-        (funcall expect 5 "This was lofty")
-        (erc-cmd-MSG "#chan hi tester")))
-
-    (with-current-buffer chan-buf-a
-      (funcall expect 5 "To employ you towards")
-      (erc-cmd-QUIT ""))
-
-    (with-current-buffer chan-buf-b
-      (funcall expect 5 "To employ you towards")
-      (erc-cmd-QUIT ""))))
-
-(ert-deftest erc-scenarios-base-network-id-same-network--two-ids ()
-  (erc-scenarios-common--base-network-id-same-network
-   (list :nick "tester"
-         :id 'tester/foonet
-         :server "tester/foonet"
-         :chan "#chan@tester/foonet")
-   (list :nick "chester"
-         :id 'chester/foonet
-         :server "chester/foonet"
-         :chan "#chan@chester/foonet")))
-
-(ert-deftest erc-scenarios-base-network-id-same-network--one-id-tester ()
-  (erc-scenarios-common--base-network-id-same-network
-   (list :nick "tester"
-         :id 'tester/foonet
-         :server "tester/foonet"
-         :chan "#chan@tester/foonet")
-   (list :nick "chester"
-         :id nil
-         :server "foonet"
-         :chan "#chan@foonet")))
-
-(ert-deftest erc-scenarios-base-network-id-same-network--one-id-chester ()
-  (erc-scenarios-common--base-network-id-same-network
-   (list :nick "tester"
-         :id nil
-         :server "foonet"
-         :chan "#chan@foonet")
-   (list :nick "chester"
-         :id 'chester/foonet
-         :server "chester/foonet"
-         :chan "#chan@chester/foonet")))
-
-(ert-deftest erc-scenarios-base-network-id-same-network--no-ids ()
-  (erc-scenarios-common--base-network-id-same-network
-   (list :nick "tester"
-         :id nil
-         :server "foonet/tester"
-         :chan "#chan@foonet/tester") ; <- note net before nick
-   (list :nick "chester"
-         :id nil
-         :server "foonet/chester"
-         :chan "#chan@foonet/chester")))
-
-;; Upon reconnecting, playback for channel and target buffers is
-;; routed correctly.  Autojoin is irrelevant here, but for the
-;; skeptical, see `erc-scenarios-common--join-network-id', which
-;; overlaps with this and includes spurious JOINs ignored by the
-;; server.
-
-(ert-deftest erc-scenarios-base-association-reconnect-playback ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/association/reconnect-playback")
-       (erc-d-linger-secs 0.5)
-       (erc-server-flood-penalty 0.1)
-       (erc-server-flood-margin 30)
-       (dumb-server (erc-d-run "localhost" t 'foonet 'foonet-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       erc-autojoin-channels-alist
-       erc-server-buffer-foo)
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
-                                       :port port
-                                       :nick "tester"
-                                       :password "changeme"
-                                       :full-name "tester"))
-      (with-current-buffer erc-server-buffer-foo
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (ert-info ("Setup")
-
-      (ert-info ("Server buffer is unique and temp name is absent")
-        (erc-d-t-wait-for 1 (get-buffer "foonet"))
-        (should-not (erc-scenarios-common-buflist "127.0.0.1")))
-
-      (ert-info ("Channel buffer #chan playback received")
-        (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
-          (funcall expect 10 "But purgatory")))
-
-      (ert-info ("Ask for help from services or bouncer bot")
-        (with-current-buffer erc-server-buffer-foo
-          (erc-cmd-MSG "*status help")))
-
-      (ert-info ("Help received")
-        (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "*status"))
-          (funcall expect 10 "Rehash")))
-
-      (ert-info ("#chan convo done")
-        (with-current-buffer "#chan"
-          (funcall expect 10 "most egregious indignity"))))
-
-    ;; KLUDGE (see note above test)
-    (should erc-autojoin-channels-alist)
-    (setq erc-autojoin-channels-alist nil)
-
-    (with-current-buffer erc-server-buffer-foo
-      (erc-cmd-QUIT "")
-      (erc-d-t-wait-for 4 (not (erc-server-process-alive)))
-      (erc-cmd-RECONNECT))
-
-    (ert-info ("Channel buffer found and associated")
-      (with-current-buffer "#chan"
-        (funcall expect 10 "Wilt thou rest damned")))
-
-    (ert-info ("Help buffer found and associated")
-      (with-current-buffer "*status"
-        (goto-char erc-input-marker)
-        (insert "help")
-        (erc-send-current-line)
-        (funcall expect 10 "Restart ZNC")))
-
-    (ert-info ("#chan convo done")
-      (with-current-buffer "#chan"
-        (funcall expect 10 "here comes the lady")))))
-
-;; You register a new nick, disconnect, and log back in, but your nick
-;; is not granted, so ERC obtains a backtick'd version.  You open a
-;; query buffer for NickServ, and ERC names it using the session ID
-;; (which includes the backtick'd nick) as a suffix.  The original
-;; (disconnected) NickServ buffer gets renamed with *its* session ID
-;; as well.  You then identify to NickServ, and the dead session is no
-;; longer considered distinct.
-
-(ert-deftest erc-scenarios-base-association-nick-bumped ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/association/nick-bump")
-       (dumb-server (erc-d-run "localhost" t 'renicked 'renicked-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.5)
-       (erc-server-flood-margin 30))
-
-    (ert-info ("Connect to foonet with nick tester")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :full-name "tester")
-        (erc-scenarios-common-assert-initial-buf-name nil port)
-        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
-
-    (ert-info ("Create an account for tester and quit")
-      (with-current-buffer "foonet"
-        (funcall expect 3 "debug mode")
-
-        (erc-cmd-QUERY "NickServ")
-        (with-current-buffer "NickServ"
-          (erc-send-input-line "NickServ" "REGISTER changeme")
-          (funcall expect 5 "Account created")
-          (funcall expect 1 "You're now logged in as tester"))
-
-        (with-current-buffer "foonet"
-          (erc-cmd-QUIT "")
-          (erc-d-t-wait-for 4 (not (erc-server-process-alive)))
-          (funcall expect 5 "ERC finished"))))
-
-    (with-current-buffer "foonet"
-      (erc-cmd-RECONNECT))
-
-    (erc-d-t-wait-for 10 "Nick request rejection prevents reassociation (good)"
-      (get-buffer "foonet/tester`"))
-
-    (ert-info ("Ask NickServ to change nick")
-      (with-current-buffer "foonet/tester`"
-        (funcall expect 3 "already in use")
-        (funcall expect 3 "debug mode")
-        (erc-cmd-QUERY "NickServ"))
-
-      (erc-d-t-wait-for 1 "Dead NickServ query buffer renamed, now qualified"
-        (get-buffer "NickServ@foonet/tester"))
-
-      (with-current-buffer "NickServ@foonet/tester`" ; new one
-        (erc-send-input-line "NickServ" "IDENTIFY tester changeme")
-        (funcall expect 5 "You're now logged in as tester")
-        (ert-info ("Original buffer found, reused")
-          (erc-d-t-wait-for 2 (equal (buffer-name) "NickServ")))))
-
-    (ert-info ("Ours is the only NickServ buffer that remains")
-      (should-not (cdr (erc-scenarios-common-buflist "NickServ"))))
-
-    (ert-info ("Visible network ID truncated to one component")
-      (should (not (get-buffer "foonet/tester`")))
-      (should (not (get-buffer "foonet/tester")))
-      (should (get-buffer "foonet")))))
-
-;; A less common variant is when your bouncer switches to an alternate
-;; nick while you're disconnected, and upon reconnecting, you get
-;; a new nick.
-
-(ert-deftest erc-scenarios-base-association-nick-bumped-mandated-renick ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/association/nick-bump")
-       (dumb-server (erc-d-run "localhost" t
-                               'renicked-foisted 'renicked-foisted-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.5)
-       (erc-server-flood-margin 30))
-
-    (ert-info ("Connect to foonet with nick tester")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :full-name "tester")
-        (erc-scenarios-common-assert-initial-buf-name nil port)
-        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
-
-    (ert-info ("Greet bob and quit")
-      (with-current-buffer "foonet"
-        (funcall expect 3 "debug mode")
-
-        (erc-cmd-QUERY "bob")
-        (with-current-buffer "bob"
-          (erc-send-input-line "bob" "hi")
-          (funcall expect 5 "hola")
-          (funcall expect 1 "how r u?"))
-
-        (with-current-buffer "foonet"
-          (erc-cmd-QUIT "")
-          (erc-d-t-wait-for 4 (not (erc-server-process-alive)))
-          (funcall expect 5 "ERC finished"))))
-
-    ;; Since we use reconnect, a new buffer won't be created
-    ;; TODO add variant with clean `erc' invocation
-    (with-current-buffer "foonet"
-      (erc-cmd-RECONNECT))
-
-    (ert-info ("Server-initiated renick")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet/dummy"))
-        (should-not (get-buffer "foonet/tester"))
-        (funcall expect 5 "debug mode"))
-
-      (erc-d-t-wait-for 1 "Old query renamed, now qualified"
-        (get-buffer "bob@foonet/tester"))
-
-      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "bob@foonet/dummy"))
-        (erc-cmd-NICK "tester")
-        (ert-info ("Buffers combined")
-          (erc-d-t-wait-for 2 (equal (buffer-name) "bob")))))
-
-    (with-current-buffer "foonet"
-      (funcall expect 5 "You're now logged in as tester"))
-
-    (ert-info ("Ours is the only bob buffer that remains")
-      (should-not (cdr (erc-scenarios-common-buflist "bob"))))
-
-    (ert-info ("Visible network ID truncated to one component")
-      (should (not (get-buffer "foonet/dummy")))
-      (should (get-buffer "foonet")))))
-
-;; Auth source consulted for initial PASS arg.  Option
-;;  `erc-connect-auth-source-host' obeyed.
-
-(defun erc-scenarios-common--auth-source (id dialog &rest rest)
-  (push "machine GNU.chat port %d user \"#chan\" password spam" rest)
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/auth-source")
-       (dumb-server (erc-d-run "localhost" t dialog))
-       (port (process-contact dumb-server :service))
-       (ents `(,@(mapcar (lambda (fmt) (format fmt port)) rest)
-               "machine MyHost port irc password 123"))
-       (netrc-file (make-temp-file "auth-source-test" nil nil
-                                   (string-join ents "\n")))
-       (auth-sources (list netrc-file))
-       (auth-source-do-cache nil)
-       (erc-scenarios-common-extra-teardown (lambda ()
-                                              (delete-file netrc-file))))
-
-    (ert-info ("Connect")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :full-name "tester"
-                                :id id)
-        (should (string= (buffer-name) (if id
-                                           (symbol-name id)
-                                         (format "127.0.0.1:%d" port))))
-        (erc-d-t-wait-for 1 (eq erc-network 'FooNet))))))
-
-(ert-deftest erc-scenarios-base-auth-source--dialed ()
-  (should (eq erc-connect-auth-source-host 'server))
-  (erc-scenarios-common--auth-source
-   nil 'foonet
-   "machine GNU.chat port %d user tester password fake"
-   "machine 127.0.0.1 port %d user tester password changeme"
-   "machine 127.0.0.1 port %d user imposter password fake"))
-
-(ert-deftest erc-scenarios-base-auth-source--dialed-fallback ()
-  (let ((erc-connect-auth-source-host t))
-    (erc-scenarios-common--auth-source
-     nil 'foonet
-     "machine FooNet port %d user tester password fake"
-     "machine 127.0.0.1 port %d user tester password changeme"
-     "machine 127.0.0.1 port %d user imposter password fake")))
-
-(ert-deftest erc-scenarios-base-auth-source--network-id ()
-  (let ((erc-connect-auth-source-host t))
-    (erc-scenarios-common--auth-source
-     'MySession 'foonet
-     "machine MySession port %d user tester password changeme"
-     "machine 127.0.0.1 port %d user tester password fake"
-     "machine FooNet port %d user tester password fake")))
-
-(ert-deftest erc-scenarios-base-auth-source--string--network-id ()
-  (let ((erc-connect-auth-source-host "MyHost"))
-    (erc-scenarios-common--auth-source
-     'MySession 'foonet
-     "machine 127.0.0.1 port %d user tester password fake"
-     "machine MyHost port %d user tester password changeme"
-     "machine MySession port %d user tester password fake")))
-
-(ert-deftest erc-scenarios-base-auth-source--nopass ()
-  (let (erc-connect-auth-source-host) ; nil
-    (erc-scenarios-common--auth-source nil 'nopass)))
-
-(ert-deftest erc-scenarios-base-auth-source--nopass--network-id ()
-  (let (erc-connect-auth-source-host) ; nil
-    (erc-scenarios-common--auth-source 'MySession 'nopass)))
-
-;; Identify via auth source with no initial password
-
-(defun erc-scenarios-common--services-auth-source (&rest rest)
-  (defvar erc-use-auth-source-for-nickserv-password)
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "services/auth-source")
-       (erc-server-flood-penalty 0.1)
-       (dumb-server (erc-d-run "localhost" t 'libera))
-       (port (process-contact dumb-server :service))
-       (ents `(,@(mapcar (lambda (fmt) (format fmt port)) rest)
-               "machine MyHost port irc password 123"))
-       (netrc-file (make-temp-file "auth-source-test" nil nil
-                                   (string-join ents "\n")))
-       (auth-sources (list netrc-file))
-       (auth-source-do-cache nil)
-       (erc-modules (cons 'services erc-modules))
-       (erc-use-auth-source-for-nickserv-password t) ; do consult for NickServ
-       (expect (erc-d-t-make-expecter))
-       (erc-scenarios-common-extra-teardown (lambda ()
-                                              (delete-file netrc-file))))
-
-    (cl-letf (((symbol-function 'read-passwd)
-               (lambda (&rest _) (error "Unexpected read-passwd call"))))
-      (ert-info ("Connect without password")
-        (with-current-buffer (erc :server "127.0.0.1"
-                                  :port port
-                                  :nick "tester"
-                                  :full-name "tester")
-          (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-          (erc-d-t-wait-for 3 (eq erc-network 'Libera.Chat))
-          (funcall expect 3 "This nickname is registered.")
-          (funcall expect 3 "You are now identified")
-          (funcall expect 3 "Last login from")
-          (erc-cmd-QUIT ""))))
-
-    (erc-services-mode -1)
-
-    (should-not (memq 'services erc-modules))))
-
-(ert-deftest erc-scenarios-services-auth-source--network ()
-  (let (erc-connect-auth-source-host) ; don't consult auth-source for PASS
-    (erc-scenarios-common--services-auth-source
-     "machine 127.0.0.1 port %d user tester password spam"
-     "machine zirconium.libera.chat port %d user tester password fake"
-     "machine Libera.Chat port %d user tester password changeme")))
-
-(ert-deftest erc-scenarios-services-auth-source--network-connect-lookup ()
-  (should (eq erc-connect-auth-source-host 'server))
-  (erc-scenarios-common--services-auth-source
-   "machine zirconium.libera.chat port %d user tester password fake"
-   "machine Libera.Chat port %d user tester password changeme"))
-
-(ert-deftest erc-scenarios-services-auth-source--announced ()
-  (let (erc-connect-auth-source-host) ; don't consult auth-source for PASS
-    (erc-scenarios-common--services-auth-source
-     "machine 127.0.0.1 port %d user tester password spam"
-     "machine zirconium.libera.chat port %d user tester password changeme")))
-
-(ert-deftest erc-scenarios-services-auth-source--dialed ()
-  (let (erc-connect-auth-source-host) ; don't consult auth-source for PASS
-    (erc-scenarios-common--services-auth-source
-     "machine 127.0.0.1 port %d user tester password changeme")))
-
-(ert-deftest erc-scenarios-services-password ()
-
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "services/password")
-       (erc-server-flood-penalty 0.1)
-       (erc-modules (cons 'services erc-modules))
-       (erc-nickserv-passwords '((Libera.Chat (("joe" . "bar")
-                                               ("tester" . "changeme")))))
-       (expect (erc-d-t-make-expecter))
-       (dumb-server (erc-d-run "localhost" t 'libera))
-       (port (process-contact dumb-server :service)))
-
-    (ert-info ("Connect without password")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (erc-d-t-wait-for 2 (eq erc-network 'Libera.Chat))
-        (funcall expect 1 "This nickname is registered.")
-        (funcall expect 1 "You are now identified")
-        (funcall expect 1 "Last login from")
-        (erc-cmd-QUIT "")))
-
-    (erc-services-mode -1)
-
-    (should-not (memq 'services erc-modules))))
-
-(ert-deftest erc-scenarios-services-prompt ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "services/password")
-       (erc-server-flood-penalty 0.1)
-       (inhibit-interaction nil)
-       (erc-modules (cons 'services erc-modules))
-       (expect (erc-d-t-make-expecter))
-       (dumb-server (erc-d-run "localhost" t 'libera))
-       (port (process-contact dumb-server :service)))
-
-    (ert-info ("Connect without password")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (ert-simulate-keys "changeme\r"
-          (erc-d-t-wait-for 2 (eq erc-network 'Libera.Chat))
-          (funcall expect 3 "This nickname is registered.")
-          (funcall expect 3 "You are now identified")
-          (funcall expect 3 "Last login from"))
-        (erc-cmd-QUIT "")))
-
-    (erc-services-mode -1)
-
-    (should-not (memq 'services erc-modules))))
-
-(ert-deftest erc-scenarios-base-flood ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/flood")
-       (erc-d-linger-secs 0.5)
-       (dumb-server (erc-d-run "localhost" t 'soju))
-       (port (process-contact dumb-server :service))
-       (erc-server-flood-penalty 0.5) ; this ratio MUST match
-       (erc-server-flood-margin 1.5) ;  the default of 3:10
-       (expect (erc-d-t-make-expecter))
-       erc-autojoin-channels-alist)
-
-    (ert-info ("Connect to bouncer")
-      (with-current-buffer
-          (erc :server "127.0.0.1"
-               :port port
-               :nick "tester"
-               :password "changeme"
-               :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (funcall expect 5 "Soju")))
-
-    (ert-info ("#chan@foonet exists")
-      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan/foonet"))
-        (erc-d-t-search-for 2 "<bob/foonet>")
-        (erc-d-t-absent-for 0.1 "<joe")
-        (funcall expect 3 "was created on")))
-
-    (ert-info ("#chan@barnet exists")
-      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan/barnet"))
-        (erc-d-t-search-for 2 "<joe/barnet>")
-        (erc-d-t-absent-for 0.1 "<bob")
-        (funcall expect 3 "was created on")
-        (funcall expect 5 "To get good guard")))
-
-    (ert-info ("Message not held in queue limbo")
-      (with-current-buffer "#chan/foonet"
-        ;; Without 'no-penalty param in `erc-server-send', should fail
-        ;; after ~10 secs with:
-        ;;
-        ;;   (erc-d-timeout "Timed out awaiting request: (:name ~privmsg
-        ;;    :pattern \\`PRIVMSG #chan/foonet :alice: hi :timeout 2
-        ;;    :dialog soju)")
-        ;;
-        ;; Try reversing commit and spying on queue interactively
-        (erc-cmd-MSG "#chan/foonet alice: hi")
-        (funcall expect 5 "tester: Good, very good")))
-
-    (ert-info ("All output sent")
-      (with-current-buffer "#chan/foonet"
-        (funcall expect 5 "Some man or other"))
-      (with-current-buffer "#chan/barnet"
-        (while (accept-process-output erc-server-process))
-        (funcall expect 5 "That's he that was Othello")))))
-
-;; Corner case demoing fallback behavior for an absent 004 RPL but a
-;; present 422 or 375.  If this is unlikely enough, remove or guard
-;; with `ert-skip' plus some condition so it only runs when explicitly
-;; named via ERT specifier
-
-(ert-deftest erc-scenarios-networks-announced-missing ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "networks/announced-missing")
-       (erc-d-linger-secs 0.5)
-       (expect (erc-d-t-make-expecter))
-       (dumb-server (erc-d-run "localhost" t 'foonet))
-       (port (process-contact dumb-server :service)))
-
-    (ert-info ("Connect without password")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (let ((err (should-error (sleep-for 1))))
-          (should (string-match-p "Failed to determine" (cadr err))))
-        (funcall expect 1 "Failed to determine")
-        (funcall expect 1 "Failed to determine")
-        (should-not erc-network)
-        (should (string= erc-server-announced-name "irc.foonet.org"))))))
-
-;; Targets that are host/server masks like $*, $$*, and #* are routed
-;; to the server buffer: https://github.com/ircdocs/wooooms/issues/5
-
-(ert-deftest erc-scenarios-base-mask-target-routing ()
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/mask-target-routing")
-       (erc-d-linger-secs 0.5)
-       (dumb-server (erc-d-run "localhost" t 'foonet))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter)))
-
-    (ert-info ("Connect to foonet")
-      (with-current-buffer (erc :server "127.0.0.1"
-                                :port port
-                                :nick "tester"
-                                :password "changeme"
-                                :full-name "tester")
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
-
-    (erc-d-t-wait-for 1 (get-buffer "foonet"))
-
-    (ert-info ("Channel buffer #foo playback received")
-      (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#foo"))
-        (funcall expect 10 "Excellent workman")))
-
-    (ert-info ("Global notices routed to server buffer")
-      (with-current-buffer "foonet"
-        (funcall expect 10 "going down soon")
-        (funcall expect 10 "this is a warning")
-        (funcall expect 10 "second warning")
-        (funcall expect 10 "final warning")))
-
-    (should-not (get-buffer "$*"))))
-
-;;; erc-scenarios.el ends here
diff --git a/test/lisp/erc/erc-d/erc-d-self.el 
b/test/lisp/erc/erc-scenarios/erc-d-self.el
similarity index 95%
rename from test/lisp/erc/erc-d/erc-d-self.el
rename to test/lisp/erc/erc-scenarios/erc-d-self.el
index c5b8f15cb2..c6e6d33bd2 100644
--- a/test/lisp/erc/erc-d/erc-d-self.el
+++ b/test/lisp/erc/erc-scenarios/erc-d-self.el
@@ -1,5 +1,7 @@
 ;;; erc-d-self.el --- tests for erc-d -*- lexical-binding: t -*-
 
+;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;;
 ;; This file is part of GNU Emacs.
 ;;
 ;; This program is free software: you can redistribute it and/or
@@ -24,14 +26,22 @@
 
 ;;; Code:
 (require 'ert-x)
-(eval-and-compile (let ((dir (getenv "EMACS_TEST_DIRECTORY")))
-                    (when dir
-                      (load (concat dir "/lisp/erc/erc-d/erc-d") nil t)
-                      (load (concat dir "/lisp/erc/erc-d/erc-d-t") nil t))))
+(eval-and-compile
+  (let ((load-path (cons (expand-file-name "erc-d" (ert-resource-directory))
+                         load-path)))
+    (require 'erc-d)
+    (require 'erc-d-t)))
+
+(require 'erc)
+
+;; For now, we need these tests to work in two different layouts:
+;; inside emacs.git, under test/lisp/erc/erc-scenarios, and as part of
+;; a standalone erc-d library.
 
-(require 'erc-d)
-(require 'erc-d-t)
-(require 'erc-backend)
+(defvar erc-d-self--resources-directory
+  (let ((default-directory (ert-resource-directory)))
+    (expand-file-name
+     (if (file-readable-p "erc-d/resources") "erc-d/resources" ""))))
 
 (ert-deftest erc-d-u--canned-load-dialog--basic ()
   (should-not (get-buffer "basic.eld"))
@@ -281,7 +291,8 @@
 (ert-deftest erc-d-i--parse-message--irc-parser-tests ()
   (let* ((data (with-temp-buffer
                  (insert-file-contents
-                  (ert-resource-file "irc-parser-tests.eld"))
+                  (expand-file-name "irc-parser-tests.eld"
+                                    erc-d-self--resources-directory))
                  (read (current-buffer))))
          (tests (assoc-default 'tests (assoc-default 'msg-split data)))
          input atoms m ours)
@@ -524,6 +535,17 @@
     (should (get-buffer "*foo*"))
     (kill-buffer "*foo*")))
 
+(ert-deftest erc-d-t-wait-for ()
+  (let (v)
+    (run-at-time 0.2 nil (lambda () (setq v t)))
+    (should (erc-d-t-wait-for 0.4 "result becomes non-nil" v))
+    (should-error (erc-d-t-wait-for 0.4 "result stays nil" (not v)))
+    (setq v nil)
+    (should-not (erc-d-t-wait-for -0.4 "inverted stays nil" v))
+    (run-at-time 0.2 nil (lambda () (setq v t)))
+    (setq v nil)
+    (should-error (erc-d-t-wait-for -0.4 "inverted becomes non-nil" v))))
+
 (defvar erc-d-self-with-server-password "changeme")
 
 ;; Compromise between removing `autojoin' from `erc-modules' entirely
@@ -581,22 +603,22 @@ DUMB-SERVER-VAR are bound accordingly in BODY."
   "Wrap functions with advice for inspecting errors caused by BODY.
 Do this for functions whose names appear in FUNC-SYMS.  When running
 advice code, add errors to list FOUND.  Note: the teardown finalizer is
-not added by default.  Also, `erc-d-linger-secs' likely has to
-be nonzero for this to work."
+not added by default.  Also, `erc-d-linger-secs' likely has to be
+nonzero for this to work."
   (declare (indent 2))
   ;; Catch errors thrown by timers that `should-error'ignores
   `(progn
-     (cl-labels ((ad (f o &rest r)
-                   (condition-case err
-                       (apply o r)
-                     (error (push err ,found)
-                            (advice-remove f 'spy)))))
+     (let ((ad (lambda (f o &rest r)
+                 (condition-case err
+                     (apply o r)
+                   (error (push err ,found)
+                          (advice-remove f 'spy))))))
        (dolist (sym ,func-syms)
-         (advice-add sym :around (apply-partially #'ad sym) '((name . spy))))
-       (progn ,@body))
-     (setq ,found (nreverse ,found))
+         (advice-add sym :around (apply-partially ad sym) '((name . spy)))))
+     (progn ,@body)
      (dolist (sym ,func-syms)
-       (advice-remove sym 'spy))))
+       (advice-remove sym 'spy))
+     (setq ,found (nreverse ,found))))
 
 (ert-deftest erc-d-run-nonstandard-messages ()
   (let* ((erc-d-linger-secs 0.2)
@@ -827,7 +849,7 @@ be nonzero for this to work."
 (defun erc-d-self--run-dynamic ()
   "Perform common assertions for \"dynamic\" dialog."
   (erc-d-self-with-server (dumb-server erc-server-buffer) dynamic
-    (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#chan"))
+    (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
       (erc-d-t-search-for 2 "tester: hey"))
     (with-current-buffer erc-server-buffer
       (let ((expect (erc-d-t-make-expecter)))
@@ -840,7 +862,7 @@ be nonzero for this to work."
 
 (ert-deftest erc-d-run-dynamic-default-match ()
   (let* (dynamic-tally
-         (erc-d-spec-vars '((user . "user")
+         (erc-d-tmpl-vars '((user . "user")
                             (ignored . ((a b) (: a space b)))
                             (realname . (group (+ graph)))))
          (nick (lambda (a)
@@ -865,7 +887,7 @@ be nonzero for this to work."
 (ert-deftest erc-d-run-dynamic-default-match-rebind ()
   (let* (tally
          ;;
-         (erc-d-spec-vars '((user . "user")
+         (erc-d-tmpl-vars '((user . "user")
                             (ignored . ((a b) (: a space b)))
                             (realname . (group (+ graph)))))
          (erc-d-match-handlers
@@ -891,7 +913,7 @@ be nonzero for this to work."
     (should (equal '(bind-nick bind-dom) tally))))
 
 (ert-deftest erc-d-run-dynamic-runtime-stub ()
-  (let ((erc-d-spec-vars '((token . (group (or "barnet" "foonet")))))
+  (let ((erc-d-tmpl-vars '((token . (group (or "barnet" "foonet")))))
         (erc-d-match-handlers
          (list :pass (lambda (d _e)
                        (erc-d-load-replacement-dialog d 'dynamic-foonet))))
@@ -909,7 +931,7 @@ be nonzero for this to work."
         (kill-buffer "#chan")))))
 
 (ert-deftest erc-d-run-dynamic-runtime-stub-skip ()
-  (let ((erc-d-spec-vars '((token . "barnet")))
+  (let ((erc-d-tmpl-vars '((token . "barnet")))
         (erc-d-match-handlers
          (list :pass (lambda (d _e)
                        (erc-d-load-replacement-dialog
@@ -978,7 +1000,7 @@ be nonzero for this to work."
 
 ;; This can be removed; only exists to get a baseline for next test
 (ert-deftest erc-d-run-fuzzy-direct ()
-  (let* ((erc-d-spec-vars
+  (let* ((erc-d-tmpl-vars
           `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t)))))
          (dumb-server (erc-d-run "localhost" t 'fuzzy))
          (dumb-server-buffer (get-buffer "*erc-d-server*"))
@@ -1020,7 +1042,7 @@ be nonzero for this to work."
 (ert-deftest erc-d-run-fuzzy ()
   (let ((erc-server-flood-penalty 1.2) ; penalty < margin/sends is basically 0
         (erc-d-linger-secs 0.1)
-        (erc-d-spec-vars
+        (erc-d-tmpl-vars
          `((now . ,(lambda () (format-time-string "%FT%T.%3NZ" nil t)))))
         erc-server-auto-reconnect)
     (erc-d-self-with-server (_ erc-server-buffer) fuzzy
@@ -1057,8 +1079,8 @@ be nonzero for this to work."
       (with-current-buffer "#bar" (funcall expect 1 "was created on"))
 
       (ert-info ("Server expects next pattern but keeps sending")
-        (with-current-buffer "#foo" (funcall expect 2 "Rosalind"))
-        (with-current-buffer "#bar" (funcall expect 1 "hi"))
+        (with-current-buffer "#foo" (funcall expect 2 "Rosalind, I will "))
+        (with-current-buffer "#bar" (funcall expect 1 "hi 123"))
         (with-current-buffer "#foo"
           (should-not (search-forward "<bob> I am heard" nil t))
           (funcall expect 1.5 "<bob> I am heard"))))))
@@ -1125,13 +1147,13 @@ bouncer-like setup."
       (kill-buffer dumb-server-buffer))))
 
 ;; This test shows the simplest way to set up template variables: put
-;; everything needed for the whole session in `erc-d-spec-vars' before
+;; everything needed for the whole session in `erc-d-tmpl-vars' before
 ;; starting the server.
 
 (ert-deftest erc-d-run-proxy-direct-spec-vars ()
   (let* ((dumb-server-buffer (get-buffer-create "*erc-d-server*"))
          (erc-d-linger-secs 0.5)
-         (erc-d-spec-vars
+         (erc-d-tmpl-vars
           `((network . (group (+ alpha)))
             (fqdn . ,(lambda (a)
                        (let ((network (funcall a :match 1 'pass)))
@@ -1183,7 +1205,7 @@ DIALOGS are symbols representing the base names of dialog 
files in
                           (if (eq (funcall a :dialog-name) 'proxy-foonet)
                               "FooNet"
                             "BarNet"))))
-         (program `(setq erc-d-spec-vars '((fqdn . ,fqdn)
+         (program `(setq erc-d-tmpl-vars '((fqdn . ,fqdn)
                                            (net . ,net)
                                            (network . (group (+ alpha))))))
          (port (erc-d-self--start-server
@@ -1196,7 +1218,8 @@ DIALOGS are symbols representing the base names of dialog 
files in
 
 (ert-deftest erc-d-run-proxy-direct-subprocess-lib ()
   (let* ((buffer (get-buffer-create "*erc-d-server*"))
-         (lib (ert-resource-file "proxy-subprocess.el"))
+         (lib (expand-file-name "proxy-subprocess.el"
+                                erc-d-self--resources-directory))
          (port (erc-d-self--start-server :linger 0.3
                                          :buffer buffer
                                          :dialogs '(proxy-foonet proxy-barnet)
@@ -1207,7 +1230,7 @@ DIALOGS are symbols representing the base names of dialog 
files in
 (ert-deftest erc-d-run-no-pong ()
   (let* (erc-d-auto-pong
          ;;
-         (erc-d-spec-vars
+         (erc-d-tmpl-vars
           `((nonce . (group (: digit digit)))
             (echo . ,(lambda (a)
                        (should (string= (funcall a :match 1) "42")) "42"))))
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-auth-source.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-auth-source.el
new file mode 100644
index 0000000000..bb8d7ecad3
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-auth-source.el
@@ -0,0 +1,178 @@
+;;; erc-scenarios-auth-source.el --- auth-source scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+;; Commentary:
+;;
+;; For practical reasons (mainly lack of imagination), this file
+;; contains tests for both server-password and NickServ contexts.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join)
+                   (require 'erc-services))
+
+(defun erc-scenarios-common--auth-source (id dialog &rest rest)
+  (push "machine GNU.chat port %d user \"#chan\" password spam" rest)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/auth-source")
+       (dumb-server (erc-d-run "localhost" t dialog))
+       (port (process-contact dumb-server :service))
+       (ents `(,@(mapcar (lambda (fmt) (format fmt port)) rest)
+               "machine MyHost port irc password 123"))
+       (netrc-file (make-temp-file "auth-source-test" nil nil
+                                   (string-join ents "\n")))
+       (auth-sources (list netrc-file))
+       (auth-source-do-cache nil)
+       (erc-scenarios-common-extra-teardown (lambda ()
+                                              (delete-file netrc-file))))
+
+    (ert-info ("Connect")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :full-name "tester"
+                                :id id)
+        (should (string= (buffer-name) (if id
+                                           (symbol-name id)
+                                         (format "127.0.0.1:%d" port))))
+        (erc-d-t-wait-for 5 (eq erc-network 'FooNet))))))
+
+(ert-deftest erc-scenarios-base-auth-source-server--dialed ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--auth-source
+   nil 'foonet
+   "machine GNU.chat port %d user tester password fake"
+   "machine FooNet port %d user tester password fake"
+   "machine 127.0.0.1 port %d user tester password changeme"
+   "machine 127.0.0.1 port %d user imposter password fake"))
+
+(ert-deftest erc-scenarios-base-auth-source-server--netid ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--auth-source
+   'MySession 'foonet
+   "machine MySession port %d user tester password changeme"
+   "machine 127.0.0.1 port %d user tester password fake"
+   "machine FooNet port %d user tester password fake"))
+
+(ert-deftest erc-scenarios-base-auth-source-server--netid-custom ()
+  :tags '(:expensive-test)
+  (let ((erc-auth-source-parameters-server-function
+         (lambda (&rest _) (list :host "MyHost"))))
+    (erc-scenarios-common--auth-source
+     'MySession 'foonet
+     "machine 127.0.0.1 port %d user tester password fake"
+     "machine MyHost port %d user tester password changeme"
+     "machine MySession port %d user tester password fake")))
+
+(ert-deftest erc-scenarios-base-auth-source-server--nopass ()
+  :tags '(:expensive-test)
+  (let (erc-auth-source-parameters-server-function)
+    (erc-scenarios-common--auth-source nil 'nopass)))
+
+(ert-deftest erc-scenarios-base-auth-source-server--nopass-netid ()
+  :tags '(:expensive-test)
+  (let (erc-auth-source-parameters-server-function)
+    (erc-scenarios-common--auth-source 'MySession 'nopass)))
+
+;; Identify via auth source with no initial password
+
+(defun erc-scenarios-common--services-auth-source (&rest rest)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "services/auth-source")
+       (erc-server-flood-penalty 0.1)
+       (dumb-server (erc-d-run "localhost" t 'libera))
+       (port (process-contact dumb-server :service))
+       (ents `(,@(mapcar (lambda (fmt) (format fmt port)) rest)
+               "machine MyHost port irc password 123"))
+       (netrc-file (make-temp-file "auth-source-test" nil nil
+                                   (string-join ents "\n")))
+       (auth-sources (list netrc-file))
+       (auth-source-do-cache nil)
+       (erc-modules (cons 'services erc-modules))
+       (erc-use-auth-source-for-nickserv-password t) ; do consult for NickServ
+       (expect (erc-d-t-make-expecter))
+       (erc-scenarios-common-extra-teardown (lambda ()
+                                              (delete-file netrc-file))))
+
+    (cl-letf (((symbol-function 'read-passwd)
+               (lambda (&rest _) (error "Unexpected read-passwd call"))))
+      (ert-info ("Connect without password")
+        (with-current-buffer (erc :server "127.0.0.1"
+                                  :port port
+                                  :nick "tester"
+                                  :full-name "tester")
+          (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+          (erc-d-t-wait-for 8 (eq erc-network 'Libera.Chat))
+          (funcall expect 3 "This nickname is registered.")
+          (funcall expect 3 "You are now identified")
+          (funcall expect 3 "Last login from")
+          (erc-cmd-QUIT ""))))
+
+    (erc-services-mode -1)
+
+    (should-not (memq 'services erc-modules))))
+
+;; These tests are about authenticating to nick services
+
+(ert-deftest erc-scenarios-services-auth-source--network ()
+  :tags '(:expensive-test)
+  ;; Skip consulting auth-source for the server password (PASS).
+  (let (erc-auth-source-parameters-server-function)
+    (erc-scenarios-common--services-auth-source
+     "machine 127.0.0.1 port %d user tester password spam"
+     "machine zirconium.libera.chat port %d user tester password fake"
+     "machine Libera.Chat port %d user tester password changeme")))
+
+(ert-deftest erc-scenarios-services-auth-source--network-connect-lookup ()
+  :tags '(:expensive-test)
+  ;; Do consult auth-source for the server password (and find nothing)
+  (erc-scenarios-common--services-auth-source
+   "machine zirconium.libera.chat port %d user tester password fake"
+   "machine Libera.Chat port %d user tester password changeme"))
+
+(ert-deftest erc-scenarios-services-auth-source--announced ()
+  :tags '(:expensive-test)
+  (let (erc-auth-source-parameters-server-function)
+    (erc-scenarios-common--services-auth-source
+     "machine 127.0.0.1 port %d user tester password spam"
+     "machine zirconium.libera.chat port %d user tester password changeme")))
+
+(ert-deftest erc-scenarios-services-auth-source--dialed ()
+  :tags '(:expensive-test)
+  ;; Support legacy host -> domain name
+  ;; (likely most common in real configs)
+  (let (erc-auth-source-parameters-server-function)
+    (erc-scenarios-common--services-auth-source
+     "machine 127.0.0.1 port %d user tester password changeme")))
+
+(ert-deftest erc-scenarios-services-auth-source--custom ()
+  :tags '(:expensive-test)
+  (let (erc-auth-source-parameters-server-function
+        (erc-auth-source-parameters-services-function
+         (lambda (&rest _) '(:host "MyAccount"))))
+    (erc-scenarios-common--services-auth-source
+     "machine zirconium.libera.chat port %d user tester password spam"
+     "machine MyAccount port %d user tester password changeme"
+     "machine 127.0.0.1 port %d user tester password fake")))
+
+;;; erc-scenarios-auth-source.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-nick.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-nick.el
new file mode 100644
index 0000000000..3152542ecf
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-nick.el
@@ -0,0 +1,164 @@
+;;; erc-scenarios-base-association-nick.el --- base assoc scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+;; You register a new nick, disconnect, and log back in, but your nick
+;; is not granted, so ERC obtains a backtick'd version.  You open a
+;; query buffer for NickServ, and ERC names it using the session ID
+;; (which includes the backtick'd nick) as a suffix.  The original
+;; (disconnected) NickServ buffer gets renamed with *its* session ID
+;; as well.  You then identify to NickServ, and the dead session is no
+;; longer considered distinct.
+
+(ert-deftest erc-scenarios-base-association-nick-bumped ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/association/nick-bump")
+       (dumb-server (erc-d-run "localhost" t 'renicked 'renicked-again))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.5)
+       (erc-server-flood-margin 30))
+
+    (ert-info ("Connect to foonet with nick tester")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :full-name "tester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)
+        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
+
+    (ert-info ("Create an account for tester and quit")
+      (with-current-buffer "foonet"
+        (funcall expect 3 "debug mode")
+
+        (erc-cmd-QUERY "NickServ")
+        (with-current-buffer "NickServ"
+          (erc-scenarios-common-say "REGISTER changeme")
+          (funcall expect 5 "Account created")
+          (funcall expect 1 "You're now logged in as tester"))
+
+        (with-current-buffer "foonet"
+          (erc-cmd-QUIT "")
+          (erc-d-t-wait-for 4 (not (erc-server-process-alive)))
+          (funcall expect 5 "ERC finished"))))
+
+    (with-current-buffer "foonet"
+      (erc-cmd-RECONNECT))
+
+    (erc-d-t-wait-for 10 "Nick request rejection prevents reassociation (good)"
+      (get-buffer "foonet/tester`"))
+
+    (ert-info ("Ask NickServ to change nick")
+      (with-current-buffer "foonet/tester`"
+        (funcall expect 3 "already in use")
+        (funcall expect 3 "debug mode")
+        (erc-cmd-QUERY "NickServ"))
+
+      (erc-d-t-wait-for 1 "Dead NickServ query buffer renamed, now qualified"
+        (get-buffer "NickServ@foonet/tester"))
+
+      (with-current-buffer "NickServ@foonet/tester`" ; new one
+        (erc-scenarios-common-say "IDENTIFY tester changeme")
+        (funcall expect 5 "You're now logged in as tester")
+        (ert-info ("Original buffer found, reused")
+          (erc-d-t-wait-for 2 (equal (buffer-name) "NickServ")))))
+
+    (ert-info ("Ours is the only NickServ buffer that remains")
+      (should-not (cdr (erc-scenarios-common-buflist "NickServ"))))
+
+    (ert-info ("Visible network ID truncated to one component")
+      (should (not (get-buffer "foonet/tester`")))
+      (should (not (get-buffer "foonet/tester")))
+      (should (get-buffer "foonet")))))
+
+;; A less common variant is when your bouncer switches to an alternate
+;; nick while you're disconnected, and upon reconnecting, you get
+;; a new nick.
+
+(ert-deftest erc-scenarios-base-association-nick-bumped-mandated-renick ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/association/nick-bump")
+       (dumb-server (erc-d-run "localhost" t
+                               'renicked-foisted 'renicked-foisted-again))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.5)
+       (erc-server-flood-margin 30))
+
+    (ert-info ("Connect to foonet with nick tester")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :full-name "tester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)
+        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
+
+    (ert-info ("Greet bob and quit")
+      (with-current-buffer "foonet"
+        (funcall expect 3 "debug mode")
+
+        (erc-cmd-QUERY "bob")
+        (with-current-buffer "bob"
+          (erc-scenarios-common-say "hi")
+          (funcall expect 5 "hola")
+          (funcall expect 1 "how r u?"))
+
+        (with-current-buffer "foonet"
+          (erc-cmd-QUIT "")
+          (erc-d-t-wait-for 4 (not (erc-server-process-alive)))
+          (funcall expect 5 "ERC finished"))))
+
+    ;; Since we use reconnect, a new buffer won't be created
+    ;; TODO add variant with clean `erc' invocation
+    (with-current-buffer "foonet"
+      (erc-cmd-RECONNECT))
+
+    (ert-info ("Server-initiated renick")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet/dummy"))
+        (should-not (get-buffer "foonet/tester"))
+        (funcall expect 15 "debug mode"))
+
+      (erc-d-t-wait-for 1 "Old query renamed, now qualified"
+        (get-buffer "bob@foonet/tester"))
+
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "bob@foonet/dummy"))
+        (erc-cmd-NICK "tester")
+        (ert-info ("Buffers combined")
+          (erc-d-t-wait-for 2 (equal (buffer-name) "bob")))))
+
+    (with-current-buffer "foonet"
+      (funcall expect 5 "You're now logged in as tester"))
+
+    (ert-info ("Ours is the only bob buffer that remains")
+      (should-not (cdr (erc-scenarios-common-buflist "bob"))))
+
+    (ert-info ("Visible network ID truncated to one component")
+      (should (not (get-buffer "foonet/dummy")))
+      (should (get-buffer "foonet")))))
+
+;;; erc-scenarios-base-association-nick.el ends here
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-samenet.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-samenet.el
new file mode 100644
index 0000000000..d27495b584
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association-samenet.el
@@ -0,0 +1,144 @@
+;;; erc-scenarios-base-association-samenet.el --- assoc samenet scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(declare-function erc-network-name "erc-networks")
+(declare-function erc-network "erc-networks")
+(defvar erc-autojoin-channels-alist)
+(defvar erc-network)
+
+;; One network, two simultaneous connections, no IDs.
+;; Reassociates on reconnect with and without server buffer.
+
+(defun erc-scenarios-common--base-association-samenet (after)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/association/same-network")
+       (dumb-server (erc-d-run "localhost" t 'tester 'chester 'tester-again))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.5)
+       (erc-server-flood-margin 30))
+
+    (ert-info ("Connect to foonet with nick tester")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :password "changeme"
+                                :full-name "tester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)
+        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
+
+    (ert-info ("Connect to foonet with nick chester")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "chester"
+                                :password "changeme"
+                                :full-name "chester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)))
+
+    (erc-d-t-wait-for 3 "Dialed Buflist is Empty"
+      (not (erc-scenarios-common-buflist "127.0.0.1")))
+
+    (with-current-buffer "foonet/tester"
+      (funcall expect 3 "debug mode")
+      (erc-cmd-JOIN "#chan"))
+
+    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/tester"))
+    (with-current-buffer "foonet/chester" (funcall expect 3 "debug mode"))
+    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/chester"))
+
+    (ert-info ("Nick tester sees other nick chester in channel")
+      (with-current-buffer "#chan@foonet/tester"
+        (funcall expect 5 "chester")
+        (funcall expect 5 "find the forester")
+        (erc-cmd-QUIT "")))
+
+    (ert-info ("Nick chester sees other nick tester in same channel")
+      (with-current-buffer  "#chan@foonet/chester"
+        (funcall expect 5 "tester")
+        (funcall expect 5 "find the forester")))
+
+    (funcall after expect)))
+
+(ert-deftest erc-scenarios-base-association-samenet--reconnect-one ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-association-samenet
+   (lambda (expect)
+
+     (ert-info ("Connection tester reconnects")
+       (with-current-buffer "foonet/tester"
+         (erc-d-t-wait-for 10 (not (erc-server-process-alive)))
+         (funcall expect 10 "*** ERC finished")
+         (erc-cmd-RECONNECT)
+         (funcall expect 5 "debug mode")))
+
+     (ert-info ("Reassociated to same channel")
+       (with-current-buffer "#chan@foonet/tester"
+         (funcall expect 5 "chester")
+         (funcall expect 5 "welcome again")
+         (erc-cmd-QUIT "")))
+
+     (with-current-buffer "#chan@foonet/chester"
+       (funcall expect 5 "tester")
+       (funcall expect 5 "welcome again")
+       (funcall expect 5 "welcome again")
+       (erc-cmd-QUIT "")))))
+
+(ert-deftest erc-scenarios-base-association-samenet--new-buffer ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-association-samenet
+   (lambda (expect)
+
+     (ert-info ("Tester kills buffer and connects from scratch")
+
+       (let (port)
+         (with-current-buffer "foonet/tester"
+           (erc-d-t-wait-for 10 (not (erc-server-process-alive)))
+           (funcall expect 10 "*** ERC finished")
+           (setq port erc-session-port)
+           (kill-buffer))
+
+         (with-current-buffer (erc :server "127.0.0.1"
+                                   :port port
+                                   :nick "tester"
+                                   :password "changeme"
+                                   :full-name "tester")
+
+           (erc-d-t-wait-for 5 (eq erc-network 'foonet)))))
+
+     (with-current-buffer "foonet/tester" (funcall expect 3 "debug mode"))
+
+     (ert-info ("Reassociated to same channel")
+       (with-current-buffer "#chan@foonet/tester"
+         (funcall expect 5 "chester")
+         (funcall expect 5 "welcome again")
+         (erc-cmd-QUIT "")))
+
+     (with-current-buffer "#chan@foonet/chester"
+       (funcall expect 5 "tester")
+       (funcall expect 5 "welcome again")
+       (funcall expect 5 "welcome again")
+       (erc-cmd-QUIT "")))))
+
+;;; erc-scenarios-base-association-samenet.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-association.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association.el
new file mode 100644
index 0000000000..ed94dfab50
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-association.el
@@ -0,0 +1,196 @@
+;;; erc-scenarios-base-association.el --- base assoc scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(declare-function erc-network-name "erc-networks")
+(declare-function erc-network "erc-networks")
+(defvar erc-autojoin-channels-alist)
+(defvar erc-network)
+
+;; Two networks, same channel name, no confusion (no bouncer).  Some
+;; of this draws from bug#47522 "foil-in-server-buf".  It shows that
+;; disambiguation-related changes added for bug#48598 are not specific
+;; to bouncers.
+
+(defun erc-scenarios-common--base-association-multi-net (second-join)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/association/multi-net")
+       (erc-server-flood-penalty 0.1)
+       (erc-d-linger-secs 1)
+       (dumb-server-foonet-buffer (get-buffer-create "*server-foonet*"))
+       (dumb-server-barnet-buffer (get-buffer-create "*server-barnet*"))
+       (dumb-server-foonet (erc-d-run "localhost" t "server-foonet" 'foonet))
+       (dumb-server-barnet (erc-d-run "localhost" t "server-barnet" 'barnet))
+       (expect (erc-d-t-make-expecter)))
+
+    (ert-info ("Connect to foonet, join #chan")
+      (with-current-buffer
+          (erc :server "127.0.0.1"
+               :port (process-contact dumb-server-foonet :service)
+               :nick "tester"
+               :password "changeme"
+               :full-name "tester")
+        (funcall expect 3 "debug mode")
+        (erc-cmd-JOIN "#chan")))
+
+    (erc-d-t-wait-for 2 (get-buffer "#chan"))
+
+    (ert-info ("Connect to barnet, join #chan")
+      (with-current-buffer
+          (erc :server "127.0.0.1"
+               :port (process-contact dumb-server-barnet :service)
+               :nick "tester"
+               :password "changeme"
+               :full-name "tester")
+        (funcall expect 3 "debug mode")))
+
+    (funcall second-join)
+
+    (erc-d-t-wait-for 3 (get-buffer "#chan@barnet"))
+
+    (erc-d-t-wait-for 2 "Buf #chan now #chan@foonet"
+      (and (get-buffer "#chan@foonet") (not (get-buffer "#chan"))))
+
+    (ert-info ("All #chan@foonet output consumed")
+      (with-current-buffer "#chan@foonet"
+        (funcall expect 3 "bob")
+        (funcall expect 3 "was created on")
+        (funcall expect 3 "prosperous")))
+
+    (ert-info ("All #chan@barnet output consumed")
+      (with-current-buffer "#chan@barnet"
+        (funcall expect 3 "mike")
+        (funcall expect 3 "was created on")
+        (funcall expect 20 "ingenuous")))))
+
+(ert-deftest erc-scenarios-base-association-multi-net--baseline ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-association-multi-net
+   (lambda () (with-current-buffer "barnet" (erc-cmd-JOIN "#chan")))))
+
+;; The /join command only targets the current buffer's process.  This
+;; recasts scenario bug#48598 "ambiguous-join" (which was based on
+;; bug#47522) to show that issuing superfluous /join commands
+;; (apparently fairly common) is benign.
+
+(ert-deftest erc-scenarios-base-association-multi-net--ambiguous-join ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-association-multi-net
+   (lambda ()
+     (ert-info ("Nonsensical JOIN attempts silently dropped.")
+       (with-current-buffer "foonet" (erc-cmd-JOIN "#chan"))
+       (sit-for 0.1)
+       (with-current-buffer "#chan" (erc-cmd-JOIN "#chan"))
+       (sit-for 0.1)
+       (erc-d-t-wait-for 2 (get-buffer "#chan"))
+       (erc-d-t-wait-for 1 "Only one #chan buffer exists"
+         (should (equal (erc-scenarios-common-buflist "#chan")
+                        (list (get-buffer "#chan")))))
+       (with-current-buffer "*server-barnet*"
+         (erc-d-t-absent-for 0.1 "JOIN"))
+       (with-current-buffer "barnet" (erc-cmd-JOIN "#chan"))))))
+
+;; Playback for same channel on two networks routed correctly.
+;; Originally from Bug#48598: 28.0.50; buffer-naming collisions
+;; involving bouncers in ERC.
+
+(ert-deftest erc-scenarios-base-association-bouncer-history ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/association/bouncer-history")
+       (erc-d-t-cleanup-sleep-secs 1)
+       (erc-d-linger-secs 2)
+       (dumb-server (erc-d-run "localhost" t 'foonet 'barnet))
+       (port (process-contact dumb-server :service))
+       (erc-server-flood-penalty 0.5)
+       (expect (erc-d-t-make-expecter))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo erc-server-process-foo
+       erc-server-buffer-bar erc-server-process-bar)
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer
+          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "foonet:changeme"
+                                           :full-name "tester"))
+        (setq erc-server-process-foo erc-server-process)
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (funcall expect 5 "foonet")))
+
+    (erc-d-t-wait-for 5 (get-buffer "#chan"))
+
+    (ert-info ("Connect to barnet")
+      (with-current-buffer
+          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "barnet:changeme"
+                                           :full-name "tester"))
+        (setq erc-server-process-bar erc-server-process)
+        (erc-d-t-wait-for 5 "Temporary name assigned"
+          (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (funcall expect 5 "barnet")))
+
+    (ert-info ("Server buffers are unique")
+      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar)))
+
+    (ert-info ("Networks correctly determined and adopted as buffer names")
+      (with-current-buffer erc-server-buffer-foo
+        (erc-d-t-wait-for 3 "network name foonet becomes buffer name"
+          (and (eq (erc-network) 'foonet) (string= (buffer-name) "foonet"))))
+      (with-current-buffer erc-server-buffer-bar
+        (erc-d-t-wait-for 3 "network name barnet becomes buffer name"
+          (and (eq (erc-network) 'barnet) (string= (buffer-name) "barnet")))))
+
+    (erc-d-t-wait-for 5 (get-buffer "#chan@barnet"))
+
+    (ert-info ("Two channel buffers created, original #chan renamed")
+      (should (= 4 (length (erc-buffer-list))))
+      (should (equal (list (get-buffer "#chan@barnet")
+                           (get-buffer "#chan@foonet"))
+                     (erc-scenarios-common-buflist "#chan"))))
+
+    (ert-info ("#chan@foonet is exclusive, no cross-contamination")
+      (with-current-buffer "#chan@foonet"
+        (erc-d-t-search-for 1 "<bob>")
+        (erc-d-t-absent-for 0.1 "<joe>")
+        (should (eq erc-server-process erc-server-process-foo))))
+
+    (ert-info ("#chan@barnet is exclusive, no cross-contamination")
+      (with-current-buffer "#chan@barnet"
+        (erc-d-t-search-for 1 "<joe>")
+        (erc-d-t-absent-for 0.1 "<bob>")
+        (should (eq erc-server-process erc-server-process-bar))))
+
+    (ert-info ("All output sent")
+      (with-current-buffer "#chan@foonet"
+        (while (accept-process-output erc-server-process-foo))
+        (erc-d-t-search-for 3 "please your lordship"))
+      (with-current-buffer "#chan@barnet"
+        (while (accept-process-output erc-server-process-bar))
+        (erc-d-t-search-for 30 "I'll bid adieu")))))
+
+;;; erc-scenarios-base-association.el ends here
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-compat-rename-bouncer.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-compat-rename-bouncer.el
new file mode 100644
index 0000000000..a513c1c933
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-compat-rename-bouncer.el
@@ -0,0 +1,175 @@
+;;; erc-scenarios-compat-rename-bouncer.el --- compat-rename scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+;; Ensure deprecated option still respected when old default value
+;; explicitly set ("respected" in the sense of having names reflect
+;; dialed TCP endpoints with possible uniquifiers but without any of
+;; the old issues, pre-bug#48598).
+
+(defun erc-scenarios-common--base-compat-no-rename-bouncer (dialogs auto more)
+  (erc-scenarios-common-with-cleanup
+      ;; These actually *are* (assigned-)network-id related because
+      ;; our kludge assigns one after the fact.
+      ((erc-scenarios-common-dialog "base/network-id/bouncer")
+       (erc-d-t-cleanup-sleep-secs 1)
+       (erc-server-flood-penalty 0.1)
+       (dumb-server (apply #'erc-d-run "localhost" t dialogs))
+       (port (process-contact dumb-server :service))
+       (chan-buf-foo (format "#chan@127.0.0.1:%d" port))
+       (chan-buf-bar (format "#chan@127.0.0.1:%d<2>" port))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-auto-reconnect auto)
+       erc-server-buffer-foo erc-server-process-foo
+       erc-server-buffer-bar erc-server-process-bar)
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer
+          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "foonet:changeme"
+                                           :full-name "tester"
+                                           :id nil))
+        (setq erc-server-process-foo erc-server-process)
+        (erc-d-t-wait-for 3 (eq (erc-network) 'foonet))
+        (erc-d-t-wait-for 3 "Final buffer name determined"
+          (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (funcall expect 5 "foonet")))
+
+    (ert-info ("Join #chan@foonet")
+      (with-current-buffer erc-server-buffer-foo (erc-cmd-JOIN "#chan"))
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
+        (funcall expect 5 "<alice>")))
+
+    (ert-info ("Connect to barnet")
+      (with-current-buffer
+          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "barnet:changeme"
+                                           :full-name "tester"
+                                           :id nil))
+        (setq erc-server-process-bar erc-server-process)
+        (erc-d-t-wait-for 3 (eq (erc-network) 'barnet))
+        (erc-d-t-wait-for 3 "Final buffer name determined"
+          (string= (buffer-name) (format "127.0.0.1:%d<2>" port)))
+        (funcall expect 5 "barnet")))
+
+    (ert-info ("Server buffers are unique, no names based on IPs")
+      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
+      (should (equal (erc-scenarios-common-buflist "127.0.0.1")
+                     (list (get-buffer (format "127.0.0.1:%d<2>" port))
+                           (get-buffer (format "127.0.0.1:%d" port))))))
+
+    (ert-info ("Join #chan@barnet")
+      (with-current-buffer erc-server-buffer-bar (erc-cmd-JOIN "#chan")))
+
+    (erc-d-t-wait-for 5 "Exactly 2 #chan-prefixed buffers exist"
+      (equal (list (get-buffer chan-buf-bar)
+                   (get-buffer chan-buf-foo))
+             (erc-scenarios-common-buflist "#chan")))
+
+    (ert-info ("#chan@127.0.0.1:$port is exclusive to foonet")
+      (with-current-buffer chan-buf-foo
+        (erc-d-t-search-for 1 "<bob>")
+        (erc-d-t-absent-for 0.1 "<joe>")
+        (should (eq erc-server-process erc-server-process-foo))
+        (while (accept-process-output erc-server-process-foo))
+        (erc-d-t-search-for 1 "ape is dead")
+        (should-not (erc-server-process-alive))))
+
+    (ert-info ("#chan@127.0.0.1:$port<2> is exclusive to barnet")
+      (with-current-buffer chan-buf-bar
+        (erc-d-t-search-for 1 "<joe>")
+        (erc-d-t-absent-for 0.1 "<bob>")
+        (should (eq erc-server-process erc-server-process-bar))
+        (while (accept-process-output erc-server-process-bar))
+        (erc-d-t-search-for 1 "keeps you from dishonour")
+        (should-not (erc-server-process-alive))))
+
+    (when more (funcall more))))
+
+(ert-deftest erc-scenarios-base-compat-no-rename-bouncer--basic ()
+  :tags '(:expensive-test)
+  (with-suppressed-warnings ((obsolete erc-rename-buffers))
+    (let (erc-rename-buffers)
+      (erc-scenarios-common--base-compat-no-rename-bouncer
+       '(foonet barnet) nil nil))))
+
+(ert-deftest erc-scenarios-base-compat-no-rename-bouncer--reconnect ()
+  :tags '(:expensive-test)
+  (let ((erc-d-tmpl-vars '((token . (group (| "barnet" "foonet")))))
+        (erc-d-match-handlers
+         (list :pass #'erc-scenarios-common--clash-rename-pass-handler))
+        (dialogs '(foonet-drop barnet-drop stub-again stub-again
+                               foonet-again barnet-again))
+        (after
+         (lambda ()
+           (pcase-let* ((`(,barnet ,foonet)
+                         (erc-scenarios-common-buflist "127.0.0.1"))
+                        (port (process-contact (with-current-buffer foonet
+                                                 erc-server-process)
+                                               :service)))
+
+             (ert-info ("Sanity check: barnet retains uniquifying suffix")
+               (should (string-suffix-p "<2>" (buffer-name barnet))))
+
+             ;; Simulate disconnection and `erc-server-auto-reconnect'
+             (ert-info ("Reconnect to foonet and barnet back-to-back")
+               (with-current-buffer foonet
+                 (erc-d-t-wait-for 5 (erc-server-process-alive)))
+               (with-current-buffer barnet
+                 (erc-d-t-wait-for 5 (erc-server-process-alive))))
+
+             (ert-info ("#chan@127.0.0.1:<port> is exclusive to foonet")
+               (with-current-buffer  (format "#chan@127.0.0.1:%d" port)
+                 (erc-d-t-search-for 1 "<alice>")
+                 (erc-d-t-absent-for 0.1 "<joe>")
+                 (while (accept-process-output erc-server-process))
+                 (erc-d-t-search-for 10 "please your lordship")))
+
+             (ert-info ("#chan@barnet is exclusive to barnet")
+               (with-current-buffer  (format "#chan@127.0.0.1:%d<2>" port)
+                 (erc-d-t-search-for 1 "<joe>")
+                 (erc-d-t-absent-for 0.1 "<bob>")
+                 (while (accept-process-output erc-server-process))
+                 (erc-d-t-search-for 1 "much in private")))
+
+             ;; Ordering deterministic here even though not so for reconnect
+             (should (equal (list barnet foonet)
+                            (erc-scenarios-common-buflist "127.0.0.1")))
+             (should (equal (list
+                             (get-buffer (format "#chan@127.0.0.1:%d<2>" port))
+                             (get-buffer (format "#chan@127.0.0.1:%d" port)))
+                            (erc-scenarios-common-buflist "#chan")))))))
+
+    (with-suppressed-warnings ((obsolete erc-rename-buffers))
+      (let (erc-rename-buffers)
+        (erc-scenarios-common--base-compat-no-rename-bouncer dialogs
+                                                             'auto after)))))
+
+;;; erc-scenarios-compat-rename-bouncer.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-misc-regressions.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-misc-regressions.el
new file mode 100644
index 0000000000..1ec18259f0
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-misc-regressions.el
@@ -0,0 +1,135 @@
+;;; erc-scenarios-base-misc-regressions.el --- misc regressions scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+;; The added complexity of a request handler definitely stinks. But on
+;; some machines, the ordering from the selector is nondeterministic,
+;; whereas normally, the filter for the last process created (in the
+;; code) gets all the initial attention. FIXME delete obsolete comment
+
+(defun erc-scenarios--rebuffed-gapless-pass-handler (dialog exchange)
+  (when (eq (erc-d-dialog-name dialog) 'pass-stub)
+    (let* ((match (erc-d-exchange-match exchange 1))
+           (sym (if (string= match "foonet") 'foonet 'barnet)))
+      (should (member match (list "foonet" "barnet")))
+      (erc-d-load-replacement-dialog dialog sym 1))))
+
+(ert-deftest erc-scenarios-base-gapless-connect ()
+  :tags '(:expensive-test)
+  "Back-to-back entry-point invocations happen successfully.
+Originally from scenario rebuffed/gapless as explained in Bug#48598:
+28.0.50; buffer-naming collisions involving bouncers in ERC."
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/gapless-connect")
+       (erc-server-flood-penalty 0.1)
+       (erc-d-linger-secs 4)
+       (erc-server-flood-penalty erc-server-flood-penalty)
+       (erc-d-tmpl-vars '((token . (group (| "barnet" "foonet")))))
+       (erc-d-match-handlers
+        (list :pass #'erc-scenarios--rebuffed-gapless-pass-handler))
+       (dumb-server (erc-d-run "localhost" t
+                               'pass-stub 'pass-stub 'barnet 'foonet))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo
+       erc-server-buffer-bar)
+
+    (ert-info ("Connect twice to same endpoint without pausing")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "foonet:changeme"
+                                       :full-name "tester")
+            erc-server-buffer-bar (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "barnet:changeme"
+                                       :full-name "tester")))
+
+    (ert-info ("Returned server buffers are unique")
+      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar)))
+
+    (ert-info ("Both connections still alive")
+      (should (get-process (format "erc-127.0.0.1-%d" port)))
+      (should (get-process (format "erc-127.0.0.1-%d<1>" port))))
+
+    (with-current-buffer erc-server-buffer-bar
+      (funcall expect 2 "marked as being away"))
+
+    (with-current-buffer (erc-d-t-wait-for 20 (get-buffer "#bar"))
+      (while (accept-process-output erc-server-process))
+      (funcall expect 2 "was created on")
+      (funcall expect 2 "his second fit"))
+
+    (with-current-buffer (erc-d-t-wait-for 20 (get-buffer "#foo"))
+      (while (accept-process-output erc-server-process))
+      (funcall expect 2 "was created on")
+      (funcall expect 2 "no use of him"))))
+
+;; This defends against a regression in `erc-server-PRIVMSG' caused by
+;; the removal of `erc-auto-query'.  When an active channel buffer is
+;; killed off and PRIVMSGs arrive targeting it, the buffer should be
+;; recreated.  See elsewhere for NOTICE logic, which is more complex.
+
+(ert-deftest erc-scenarios-base-channel-buffer-revival ()
+  :tags '(:expensive-test)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/channel-buffer-revival")
+       (erc-d-linger-secs 0.5)
+       (dumb-server (erc-d-run "localhost" t 'foonet))
+       (port (process-contact dumb-server :service))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (ert-info ("Server buffer is unique and temp name is absent")
+      (erc-d-t-wait-for 1 (get-buffer "FooNet"))
+      (should-not (erc-scenarios-common-buflist "127.0.0.1"))
+      (with-current-buffer erc-server-buffer-foo
+        (erc-cmd-JOIN "#chan")))
+
+    (ert-info ("Channel buffer #chan alive and well")
+      (with-current-buffer (erc-d-t-wait-for 8 (get-buffer "#chan"))
+        (erc-d-t-search-for 10 "Our queen and all her elves")
+        (kill-buffer)))
+
+    (should-not (get-buffer "#chan"))
+
+    (ert-info ("Channel buffer #chan revived")
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
+        (erc-d-t-search-for 10 "and be prosperous")))))
+
+;;; erc-scenarios-base-misc-regressions.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
new file mode 100644
index 0000000000..e4db3fc054
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-id.el
@@ -0,0 +1,34 @@
+;;; erc-scenarios-base-netid-bouncer-id.el --- net-id bouncer ID scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-base-netid-bouncer--id-foo ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer '(:foo-id t) 'foonet 'barnet))
+
+(ert-deftest erc-scenarios-base-netid-bouncer--id-bar ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer '(:bar-id t) 'foonet 'barnet))
+
+;;; erc-scenarios-base-netid-bouncer-id.el ends here
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el
new file mode 100644
index 0000000000..681e6cd469
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-base.el
@@ -0,0 +1,30 @@
+;;; erc-scenarios-base-netid-bouncer-recon-base.el --- net-id base scenarios 
-*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-base-netid-bouncer--recon-base ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer--reconnect nil nil))
+
+;;; erc-scenarios-base-netid-bouncer-recon-base.el ends here
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-both.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-both.el
new file mode 100644
index 0000000000..73f9efdc8c
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-both.el
@@ -0,0 +1,32 @@
+;;; erc-scenarios-base-netid-bouncer-recon-both.el --- net-id both scenarios 
-*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(require 'erc-scenarios-common)
+
+(ert-deftest erc-scenarios-base-netid-bouncer--recon-both ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer--reconnect 'foo-id 'bar-id))
+
+;;; erc-scenarios-base-netid-bouncer-recon-both.el ends here
diff --git 
a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-id.el
new file mode 100644
index 0000000000..132d7eeb03
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer-recon-id.el
@@ -0,0 +1,35 @@
+;;; erc-scenarios-base-netid-bouncer-recon-id.el --- recon ID scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-base-netid-bouncer--reconnect-id-foo ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer--reconnect 'foo-id nil))
+
+(ert-deftest erc-scenarios-base-netid-bouncer--reconnect-id-bar ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer--reconnect nil 'bar-id))
+
+
+;;; erc-scenarios-base-netid-bouncer-recon-id.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer.el
new file mode 100644
index 0000000000..bd71e81bb5
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-bouncer.el
@@ -0,0 +1,35 @@
+;;; erc-scenarios-base-netid-bouncer.el --- net-id bouncer scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-base-netid-bouncer--base ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer () 'foonet 'barnet))
+
+(ert-deftest erc-scenarios-base-netid-bouncer--both ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-bouncer '(:foo-id t :bar-id t)
+                                                 'foonet 'barnet))
+
+;;; erc-scenarios-base-netid-bouncer.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-samenet.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-samenet.el
new file mode 100644
index 0000000000..86b0388eec
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-netid-samenet.el
@@ -0,0 +1,147 @@
+;;; erc-scenarios-base-network-id-samenet.el --- netid-id samenet scenarios 
-*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+(cl-defun erc-scenarios-common--base-network-id-same-network
+    ((&key nick id server chan
+           &aux (nick-a nick) (id-a id) (serv-buf-a server) (chan-buf-a chan))
+     (&key nick id server chan
+           &aux (nick-b nick) (id-b id) (serv-buf-b server) (chan-buf-b chan)))
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/network-id/same-network")
+       (dumb-server (erc-d-run "localhost" t 'tester 'chester))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.1)
+       (erc-server-flood-margin 30)
+       erc-serv-buf-a erc-serv-buf-b)
+
+    (ert-info ("Connect to foonet with nick tester")
+      (with-current-buffer
+          (setq erc-serv-buf-a (erc :server "127.0.0.1"
+                                    :port port
+                                    :nick nick-a
+                                    :password "changeme"
+                                    :full-name nick-a
+                                    :id id-a))
+        (erc-scenarios-common-assert-initial-buf-name id-a port)
+        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
+
+    (ert-info ("Connect to foonet with nick chester")
+      (with-current-buffer
+          (setq erc-serv-buf-b (erc :server "127.0.0.1"
+                                    :port port
+                                    :nick nick-b
+                                    :password "changeme"
+                                    :full-name nick-b
+                                    :id id-b))
+        (erc-scenarios-common-assert-initial-buf-name id-b port)))
+
+    (erc-d-t-wait-for 3 (not (erc-scenarios-common-buflist "127.0.0.1")))
+
+    (with-current-buffer erc-serv-buf-a
+      (should (string= (buffer-name) serv-buf-a))
+      (funcall expect 8 "debug mode")
+      (erc-cmd-JOIN "#chan"))
+
+    (with-current-buffer erc-serv-buf-b
+      (should (string= (buffer-name) serv-buf-b))
+      (funcall expect 8 "debug mode")
+      (erc-cmd-JOIN "#chan"))
+
+    (erc-d-t-wait-for 10 (get-buffer chan-buf-a))
+    (erc-d-t-wait-for 10 (get-buffer chan-buf-b))
+
+    (ert-info ("Greets other nick in same channel")
+      (with-current-buffer chan-buf-a
+        (funcall expect 5 "chester")
+        (funcall expect 5 "find the forester")
+        (erc-cmd-MSG "#chan chester: hi")))
+
+    (ert-info ("Sees other nick in same channel")
+      (with-current-buffer chan-buf-b
+        (funcall expect 5 "tester")
+        (funcall expect 10 "<tester> chester: hi")
+        (funcall expect 5 "This was lofty")
+        (erc-cmd-MSG "#chan hi tester")))
+
+    (with-current-buffer chan-buf-a
+      (funcall expect 5 "To employ you towards")
+      (erc-cmd-QUIT ""))
+
+    (with-current-buffer chan-buf-b
+      (funcall expect 5 "To employ you towards")
+      (erc-cmd-QUIT ""))))
+
+(ert-deftest erc-scenarios-base-network-id-same-network--two-ids ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-same-network
+   (list :nick "tester"
+         :id 'tester/foonet
+         :server "tester/foonet"
+         :chan "#chan@tester/foonet")
+   (list :nick "chester"
+         :id 'chester/foonet
+         :server "chester/foonet"
+         :chan "#chan@chester/foonet")))
+
+(ert-deftest erc-scenarios-base-network-id-same-network--one-id-tester ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-same-network
+   (list :nick "tester"
+         :id 'tester/foonet
+         :server "tester/foonet"
+         :chan "#chan@tester/foonet")
+   (list :nick "chester"
+         :id nil
+         :server "foonet"
+         :chan "#chan@foonet")))
+
+(ert-deftest erc-scenarios-base-network-id-same-network--one-id-chester ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-same-network
+   (list :nick "tester"
+         :id nil
+         :server "foonet"
+         :chan "#chan@foonet")
+   (list :nick "chester"
+         :id 'chester/foonet
+         :server "chester/foonet"
+         :chan "#chan@chester/foonet")))
+
+(ert-deftest erc-scenarios-base-network-id-same-network--no-ids ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common--base-network-id-same-network
+   (list :nick "tester"
+         :id nil
+         :server "foonet/tester"
+         :chan "#chan@foonet/tester") ; <- note net before nick
+   (list :nick "chester"
+         :id nil
+         :server "foonet/chester"
+         :chan "#chan@foonet/chester")))
+
+;;; erc-scenarios-base-network-id-samenet.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-reconnect.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reconnect.el
new file mode 100644
index 0000000000..56b7570c29
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reconnect.el
@@ -0,0 +1,227 @@
+;;; erc-scenarios-base-reconnect.el --- Base-reconnect scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+;; This ensures we only reconnect `erc-server-reconnect-attempts'
+;; (rather than infinitely many) times, which can easily happen when
+;; tweaking code related to process sentinels in erc-backend.el.
+
+(ert-deftest erc-scenarios-base-reconnect-timer ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/reconnect")
+       (dumb-server (erc-d-run "localhost" t 'timer 'timer 'timer-last))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-auto-reconnect t)
+       erc-autojoin-channels-alist
+       erc-server-buffer)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer (erc :server "127.0.0.1"
+                                   :port port
+                                   :nick "tester"
+                                   :password "changeme"
+                                   :full-name "tester"))
+      (with-current-buffer erc-server-buffer
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (ert-info ("Server tries to connect thrice (including initial attempt)")
+      (with-current-buffer erc-server-buffer
+        (dotimes (n 3)
+          (ert-info ((format "Attempt %d" n))
+            (funcall expect 3 "Opening connection")
+            (funcall expect 2 "Password incorrect")
+            (funcall expect 2 "Connection failed!")
+            (funcall expect 2 "Re-establishing connection")))
+        (ert-info ("Prev attempt was final")
+          (erc-d-t-absent-for 1 "Opening connection" (point)))))
+
+    (ert-info ("Server buffer is unique and temp name is absent")
+      (should (equal (list (get-buffer (format "127.0.0.1:%d" port)))
+                     (erc-scenarios-common-buflist "127.0.0.1"))))))
+
+(defun erc-scenarios-common--base-reconnect-options (test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/reconnect")
+       (dumb-server (erc-d-run "localhost" t 'options 'options-again))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.1)
+       (erc-server-auto-reconnect t)
+       erc-autojoin-channels-alist
+       erc-server-buffer)
+
+    (should (memq 'autojoin erc-modules))
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer (erc :server "127.0.0.1"
+                                   :port port
+                                   :nick "tester"
+                                   :password "changeme"
+                                   :full-name "tester"))
+      (with-current-buffer erc-server-buffer
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (funcall expect 10 "debug mode")))
+
+    (ert-info ("Wait for some output in channels")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall expect 10 "welcome")))
+
+    (ert-info ("Server buffer shows connection failed")
+      (with-current-buffer erc-server-buffer
+        (funcall expect 10 "Connection failed!  Re-establishing")))
+
+    (should (equal erc-autojoin-channels-alist '((FooNet "#chan"))))
+
+    (funcall test)
+
+    (with-current-buffer "FooNet" (erc-cmd-JOIN "#spam"))
+
+    (erc-d-t-wait-for 5 "Channel #spam shown when autojoined"
+      (eq (window-buffer) (get-buffer "#spam")))
+
+    (ert-info ("Wait for auto reconnect")
+      (with-current-buffer erc-server-buffer
+        (funcall expect 10 "still in debug mode")))
+
+    (ert-info ("Wait for activity to recommence in channels")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall expect 10 "forest of Arden"))
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
+        (funcall expect 10 "her elves come here anon")))))
+
+(ert-deftest erc-scenarios-base-reconnect-options--default ()
+  :tags '(:expensive-test)
+  (should (eq erc-join-buffer 'buffer))
+  (should-not erc-reconnect-display)
+
+  ;; FooNet (the server buffer) is not switched to because it's
+  ;; already current (but not shown) when `erc-open' is called.  See
+  ;; related conditional guard towards the end of that function.
+
+  (erc-scenarios-common--base-reconnect-options
+   (lambda ()
+     (pop-to-buffer-same-window "*Messages*")
+
+     (erc-d-t-ensure-for 1 "Server buffer not shown"
+       (not (eq (window-buffer) (get-buffer "FooNet"))))
+
+     (erc-d-t-wait-for 5 "Channel #chan shown when autojoined"
+       (eq (window-buffer) (get-buffer "#chan"))))))
+
+(ert-deftest erc-scenarios-base-reconnect-options--bury ()
+  :tags '(:expensive-test)
+  (should (eq erc-join-buffer 'buffer))
+  (should-not erc-reconnect-display)
+
+  (let ((erc-reconnect-display 'bury))
+    (erc-scenarios-common--base-reconnect-options
+
+     (lambda ()
+       (pop-to-buffer-same-window "*Messages*")
+
+       (erc-d-t-ensure-for 1 "Server buffer not shown"
+         (not (eq (window-buffer) (get-buffer "FooNet"))))
+
+       (erc-d-t-ensure-for 3 "Channel #chan not shown"
+         (not (eq (window-buffer) (get-buffer "#chan"))))
+
+       (eq (window-buffer) (messages-buffer))))))
+
+;; Upon reconnecting, playback for channel and target buffers is
+;; routed correctly.  Autojoin is irrelevant here, but for the
+;; skeptical, see `erc-scenarios-common--join-network-id', which
+;; overlaps with this and includes spurious JOINs ignored by the
+;; server.
+
+(ert-deftest erc-scenarios-base-association-reconnect-playback ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/association/reconnect-playback")
+       (erc-d-linger-secs 0.5)
+       (erc-server-flood-penalty 0.1)
+       (erc-server-flood-margin 30)
+       (dumb-server (erc-d-run "localhost" t 'foonet 'foonet-again))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (ert-info ("Setup")
+
+      (ert-info ("Server buffer is unique and temp name is absent")
+        (erc-d-t-wait-for 3 (get-buffer "foonet"))
+        (should-not (erc-scenarios-common-buflist "127.0.0.1")))
+
+      (ert-info ("Channel buffer #chan playback received")
+        (with-current-buffer (erc-d-t-wait-for 8 (get-buffer "#chan"))
+          (funcall expect 10 "But purgatory")))
+
+      (ert-info ("Ask for help from services or bouncer bot")
+        (with-current-buffer erc-server-buffer-foo
+          (erc-cmd-MSG "*status help")))
+
+      (ert-info ("Help received")
+        (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "*status"))
+          (funcall expect 10 "Rehash")))
+
+      (ert-info ("#chan convo done")
+        (with-current-buffer "#chan"
+          (funcall expect 10 "most egregious indignity"))))
+
+    ;; KLUDGE (see note above test)
+    (should erc-autojoin-channels-alist)
+    (setq erc-autojoin-channels-alist nil)
+
+    (with-current-buffer erc-server-buffer-foo
+      (erc-cmd-QUIT "")
+      (erc-d-t-wait-for 4 (not (erc-server-process-alive)))
+      (erc-cmd-RECONNECT))
+
+    (ert-info ("Channel buffer found and associated")
+      (with-current-buffer "#chan"
+        (funcall expect 10 "Wilt thou rest damned")))
+
+    (ert-info ("Help buffer found and associated")
+      (with-current-buffer "*status"
+        (erc-scenarios-common-say "help")
+        (funcall expect 10 "Restart ZNC")))
+
+    (ert-info ("#chan convo done")
+      (with-current-buffer "#chan"
+        (funcall expect 10 "here comes the lady")))))
+
+;;; erc-scenarios-base-reconnect.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-renick.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-renick.el
new file mode 100644
index 0000000000..e10aa5cc17
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-renick.el
@@ -0,0 +1,310 @@
+;;; erc-scenarios-base-renick.el --- Re-nicking scenarios -*- lexical-binding: 
t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+;; The server changes your nick just after registration.
+
+(ert-deftest erc-scenarios-base-renick-self-auto ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/renick/self")
+       (erc-d-linger-secs 0.1)
+       (erc-server-flood-penalty 0.1)
+       (dumb-server (erc-d-run "localhost" t 'auto))
+       (port (process-contact dumb-server :service))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "foonet:changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "foonet"))
+      (erc-d-t-search-for 10 "Your new nickname is dummy"))
+
+    (ert-info ("Joined by bouncer to #foo, own nick present")
+      (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
+        (erc-d-t-search-for 10 "dummy")
+        (erc-d-t-search-for 10 "On Thursday")))))
+
+;; You change your nickname manually in a server buffer; a message is
+;; printed in channel buffers.
+
+(ert-deftest erc-scenarios-base-renick-self-manual ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/renick/self")
+       (erc-d-linger-secs 0.1)
+       (erc-server-flood-penalty 0.1)
+       (dumb-server (erc-d-run "localhost" t 'manual))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "foonet:changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (erc-d-t-wait-for 3 (get-buffer "foonet"))
+
+    (ert-info ("Joined by bouncer to #foo, own nick present")
+      (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
+        (funcall expect 5 "tester")
+        (funcall expect 5 "On Thursday")
+        (erc-with-server-buffer (erc-cmd-NICK "dummy"))
+        (funcall expect 5 "Your new nickname is dummy")
+        (funcall expect 5 "<bob> dummy: Hi")
+        ;; Regression in which changing a nick would trigger #foo@foonet
+        (erc-d-t-ensure-for 0.4 (equal (buffer-name) "#foo"))))))
+
+;; You connect to the same network with two different nicks.  You
+;; manually change the first nick at some point, and buffer names are
+;; updated correctly.
+
+(ert-deftest erc-scenarios-base-renick-self-qualified ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/renick/self")
+       (dumb-server (erc-d-run "localhost" t 'qual-tester 'qual-chester))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.1)
+       (erc-server-flood-margin 30)
+       erc-serv-buf-a erc-serv-buf-b)
+
+    (ert-info ("Connect to foonet with nick tester")
+      (with-current-buffer
+          (setq erc-serv-buf-a (erc :server "127.0.0.1"
+                                    :port port
+                                    :nick "tester"
+                                    :password "changeme"
+                                    :full-name "tester"))
+        (erc-d-t-wait-for 5 (eq erc-network 'foonet))))
+
+    (ert-info ("Connect to foonet with nick chester")
+      (with-current-buffer
+          (setq erc-serv-buf-b (erc :server "127.0.0.1"
+                                    :port port
+                                    :nick "chester"
+                                    :password "changeme"
+                                    :full-name "chester"))))
+
+    (erc-d-t-wait-for 3 "Dialed Buflist is Empty"
+      (not (erc-scenarios-common-buflist "127.0.0.1")))
+
+    (with-current-buffer  "foonet/tester"
+      (funcall expect 3 "debug mode")
+      (erc-cmd-JOIN "#chan"))
+
+    (with-current-buffer  "foonet/chester"
+      (funcall expect 3 "debug mode")
+      (erc-cmd-JOIN "#chan"))
+
+    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/tester"))
+    (erc-d-t-wait-for 10 (get-buffer "#chan@foonet/chester"))
+
+    (ert-info ("Greets other nick in same channel")
+      (with-current-buffer "#chan@foonet/tester"
+        (funcall expect 5 "<bob> chester, welcome!")
+        (erc-cmd-NICK "dummy")
+        (funcall expect 5 "Your new nickname is dummy")
+        (funcall expect 5 "find the forester")
+        (erc-d-t-wait-for 5 (string= (buffer-name) "#chan@foonet/dummy"))))
+
+    (ert-info ("Renick propagated throughout all buffers of process")
+      (should-not (get-buffer "#chan@foonet/tester"))
+      (should-not (get-buffer "foonet/tester"))
+      (should (get-buffer "foonet/dummy")))))
+
+;; When a channel user changes their nick, any query buffers for them
+;; are updated.
+
+(ert-deftest erc-scenarios-base-renick-queries-solo ()
+  :tags '(:expensive-test)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/renick/queries")
+       (erc-d-linger-secs 0.1)
+       (erc-server-flood-penalty 0.1)
+       (erc-server-flood-margin 20)
+       (dumb-server (erc-d-run "localhost" t 'solo))
+       (port (process-contact dumb-server :service))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "foonet:changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (erc-d-t-wait-for 1 (get-buffer "foonet"))
+
+    (ert-info ("Joined by bouncer to #foo, pal persent")
+      (with-current-buffer (erc-d-t-wait-for 1 (get-buffer "#foo"))
+        (erc-d-t-search-for 1 "On Thursday")
+        (erc-scenarios-common-say "hi")))
+
+    (erc-d-t-wait-for 10 "Query buffer appears with message from pal"
+      (get-buffer "Lal"))
+
+    (ert-info ("Chat with pal, who changes name")
+      (with-current-buffer "Lal"
+        (erc-d-t-search-for 3 "hello")
+        (erc-scenarios-common-say "hi")
+        (erc-d-t-search-for 10 "is now known as Linguo")
+        (should-not (search-forward "is now known as Linguo" nil t))))
+
+    (erc-d-t-wait-for 1 (get-buffer "Linguo"))
+    (should-not (get-buffer "Lal"))
+
+    (with-current-buffer "Linguo" (erc-scenarios-common-say "howdy Linguo"))
+
+    (with-current-buffer "#foo"
+      (erc-d-t-search-for 10 "is now known as Linguo")
+      (should-not (search-forward "is now known as Linguo" nil t))
+      (erc-cmd-PART ""))
+
+    (with-current-buffer "Linguo"
+      (erc-d-t-search-for 10 "get along"))))
+
+;; You share a channel and a query buffer with a user on two different
+;; networks (through a proxy).  The user changes their nick on both
+;; networks at the same time.  Query buffers are updated accordingly.
+
+(ert-deftest erc-scenarios-base-renick-queries-bouncer ()
+  :tags '(:expensive-test)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/renick/queries")
+       (erc-d-linger-secs 1.5)
+       (erc-server-flood-penalty 0.1)
+       (erc-server-flood-margin 30)
+       (dumb-server (erc-d-run "localhost" t 'bouncer-foonet 'bouncer-barnet))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       erc-accidental-paste-threshold-seconds
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo
+       erc-server-buffer-bar)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "foonet:changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (erc-d-t-wait-for 5 (get-buffer "foonet"))
+
+    (ert-info ("Connect to barnet")
+      (setq erc-server-buffer-bar (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "barnet:changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-bar
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (erc-d-t-wait-for 5 (get-buffer "barnet"))
+    (should-not (erc-scenarios-common-buflist "127.0.0.1"))
+
+    (ert-info ("Joined by bouncer to #chan@foonet, pal persent")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan@foonet"))
+        (funcall expect 1 "rando")
+        (funcall expect 1 "simply misused")))
+
+    (ert-info ("Joined by bouncer to #chan@barnet, pal persent")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan@barnet"))
+        (funcall expect 1 "rando")
+        (funcall expect 1 "come, sir, I am")))
+
+    (ert-info ("Query buffer exists for rando@foonet")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "rando@foonet"))
+        (funcall expect 1 "guess not")
+        (erc-scenarios-common-say "I here")))
+
+    (ert-info ("Query buffer exists for rando@barnet")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "rando@barnet"))
+        (funcall expect 2 "rentacop")
+        (erc-scenarios-common-say "Linda said you were gonna kill me.")))
+
+    (ert-info ("Sync convo for rando@foonet")
+      (with-current-buffer "rando@foonet"
+        (funcall expect 1 "u are dumb")
+        (erc-scenarios-common-say "not so")))
+
+    (ert-info ("Sync convo for rando@barnet")
+      (with-current-buffer "rando@barnet"
+        (funcall expect 3 "I never saw her before")
+        (erc-scenarios-common-say "You aren't with Wage?")))
+
+    (erc-d-t-wait-for 3 (get-buffer "frenemy@foonet"))
+    (erc-d-t-wait-for 3 (get-buffer "frenemy@barnet"))
+    (should-not (get-buffer "rando@foonet"))
+    (should-not (get-buffer "rando@barnet"))
+
+    (with-current-buffer "frenemy@foonet"
+      (funcall expect 1 "now known as")
+      (funcall expect 1 "doubly so"))
+
+    (with-current-buffer "frenemy@barnet"
+      (funcall expect 1 "now known as")
+      (funcall expect 1 "reality picture"))
+
+    (when noninteractive
+      (with-current-buffer "frenemy@barnet" (kill-buffer))
+      (erc-d-t-wait-for 2 (get-buffer "frenemy"))
+      (should-not (get-buffer "frenemy@foonet")))
+
+    (with-current-buffer "#chan@foonet"
+      (funcall expect 10 "is now known as frenemy")
+      (should-not (search-forward "now known as frenemy" nil t)) ; regression
+      (funcall expect 10 "words are razors"))
+
+    (with-current-buffer "#chan@barnet"
+      (funcall expect 10 "is now known as frenemy")
+      (should-not (search-forward "now known as frenemy" nil t))
+      (while (accept-process-output erc-server-process))
+      (erc-d-t-search-for 25 "I have lost"))))
+
+;;; erc-scenarios-base-renick.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-reuse-buffers.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reuse-buffers.el
new file mode 100644
index 0000000000..fa10e592ec
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-reuse-buffers.el
@@ -0,0 +1,189 @@
+;;; erc-scenarios-base-reuse-buffers.el --- base-reuse-buffers scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+(defun erc-scenarios-common--base-reuse-buffers-server-buffers (&optional more)
+  "Show that `erc-reuse-buffers' doesn't affect server buffers.
+Overlaps some with `clash-of-chans/uniquify'.  Adapted from
+rebuffed/reuseless, described in Bug#48598: 28.0.50; buffer-naming
+collisions involving bouncers in ERC.  Run EXTRA."
+  (erc-scenarios-common-with-cleanup
+      ((erc-d-linger-secs 1)
+       (dumb-server (erc-d-run "localhost" t 'foonet 'barnet))
+       (port (process-contact dumb-server :service))
+       erc-autojoin-channels-alist)
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :password "foonet:changeme"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (erc-d-t-search-for 12 "marked as being away")))
+
+    (ert-info ("Connect to barnet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :password "barnet:changeme"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (erc-d-t-search-for 45 "marked as being away")))
+
+    (erc-d-t-wait-for 2 (get-buffer "foonet"))
+    (erc-d-t-wait-for 2 (get-buffer "barnet"))
+
+    (ert-info ("Server buffers are unique, no IP-based names")
+      (should-not (eq (get-buffer "foonet") (get-buffer "barnet")))
+      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
+
+    (when more (funcall more))))
+
+(ert-deftest erc-scenarios-base-reuse-buffers-server-buffers--enabled ()
+  :tags '(:expensive-test)
+  (should erc-reuse-buffers)
+  (let ((erc-scenarios-common-dialog "base/reuse-buffers/server-buffers"))
+    (erc-scenarios-common--base-reuse-buffers-server-buffers)))
+
+(ert-deftest erc-scenarios-base-reuse-buffers-server-buffers--disabled ()
+  :tags '(:expensive-test)
+  (should erc-reuse-buffers)
+  (let ((erc-scenarios-common-dialog "base/reuse-buffers/server-buffers")
+        erc-reuse-buffers)
+    (erc-scenarios-common--base-reuse-buffers-server-buffers)))
+
+;; This also asserts that `erc-cmd-JOIN' is no longer susceptible to a
+;; regression introduced in 28.1 (ERC 5.4) that caused phantom target
+;; buffers of the form target/server to be created via
+;; `switch-to-buffer' ("phantom" because they would go unused").  This
+;; would happen (in place of a JOIN being sent out) when a previously
+;; used (parted) target buffer existed and `erc-reuse-buffers' was
+;; nil.
+;;
+;; Note: All the `erc-get-channel-user' calls have to do with the fact
+;; that `erc-default-target' relies on the less-than-well-defined
+;; `erc-default-recipients' and is thus overloaded in the sense of
+;; being used both for retrieving a target name and checking if
+;; channel has been PARTed.  While not ideal, `erc-get-channel-user'
+;; can (also) be used to detect the latter.
+
+(defun erc-scenarios-common--base-reuse-buffers-channel-buffers ()
+  "The option `erc-reuse-buffers' is still respected when nil.
+Adapted from scenario clash-of-chans/uniquify described in Bug#48598:
+28.0.50; buffer-naming collisions involving bouncers in ERC."
+  (let ((expect (erc-d-t-make-expecter))
+        (server-process-bar (with-current-buffer "barnet" erc-server-process))
+        (server-process-foo (with-current-buffer "foonet" erc-server-process)))
+
+    (ert-info ("Unique #chan buffers exist")
+      (let ((chan-bufs (erc-scenarios-common-buflist "#chan"))
+            (names '("#chan@barnet" "#chan@foonet")))
+        (should (member (buffer-name (pop chan-bufs)) names))
+        (should (member (buffer-name (pop chan-bufs)) names))
+        (should-not chan-bufs)))
+
+    (ert-info ("#chan@foonet is exclusive and not contaminated")
+      (with-current-buffer "#chan@foonet"
+        (funcall expect 1 "<bob>")
+        (erc-d-t-absent-for 0.1 "<joe>")
+        (funcall expect 1 "strength to climb")
+        (should (eq erc-server-process server-process-foo))))
+
+    (ert-info ("#chan@barnet is exclusive and not contaminated")
+      (with-current-buffer "#chan@barnet"
+        (funcall expect 1 "<joe>")
+        (erc-d-t-absent-for 0.1 "<bob>")
+        (funcall expect 1 "the loudest noise")
+        (should (eq erc-server-process server-process-bar))))
+
+    (ert-info ("Part #chan@foonet")
+      (with-current-buffer "#chan@foonet"
+        (erc-d-t-search-for 1 "shake my sword")
+        (erc-cmd-PART "#chan")
+        (funcall expect 3 "You have left channel #chan")
+        (erc-cmd-JOIN "#chan")))
+
+    (ert-info ("Part #chan@barnet")
+      (with-current-buffer "#chan@barnet"
+        (funcall expect 3 "Arm it in rags")
+        (should (erc-get-channel-user (erc-current-nick)))
+        (erc-cmd-PART "#chan")
+        (funcall expect 3 "You have left channel #chan")
+        (should-not (erc-get-channel-user (erc-current-nick)))
+        (erc-cmd-JOIN "#chan")))
+
+    (erc-d-t-wait-for 3 "New unique target buffer for #chan@foonet created"
+      (get-buffer "#chan@foonet<2>"))
+
+    (ert-info ("Activity continues in new, <n>-suffixed #chan@foonet buffer")
+      (with-current-buffer "#chan@foonet"
+        (should-not (erc-get-channel-user (erc-current-nick))))
+      (with-current-buffer "#chan@foonet<2>"
+        (should (erc-get-channel-user (erc-current-nick)))
+        (funcall expect 2 "You have joined channel #chan")
+        (funcall expect 2 "#chan was created on")
+        (funcall expect 2 "<alice>")
+        (should (eq erc-server-process server-process-foo))
+        (erc-d-t-absent-for 0.2 "<joe>")))
+
+    (erc-d-t-wait-for 3 "New unique target buffer for #chan@barnet created"
+      (get-buffer "#chan@barnet<2>"))
+
+    (ert-info ("Activity continues in new, <n>-suffixed #chan@barnet buffer")
+      (with-current-buffer "#chan@barnet"
+        (should-not (erc-get-channel-user (erc-current-nick))))
+      (with-current-buffer "#chan@barnet<2>"
+        (funcall expect 2 "You have joined channel #chan")
+        (funcall expect 1 "Users on #chan: @mike joe tester")
+        (funcall expect 2 "<mike>")
+        (should (eq erc-server-process server-process-bar))
+        (erc-d-t-absent-for 0.2 "<bob>")))
+
+    (ert-info ("Two new chans created for a total of four")
+      (let* ((bufs (erc-scenarios-common-buflist "#chan"))
+             (names (sort (mapcar #'buffer-name bufs) #'string<)))
+        (should (equal names '("#chan@barnet" "#chan@barnet<2>"
+                               "#chan@foonet" "#chan@foonet<2>")))))
+
+    (ert-info ("All output sent")
+      (with-current-buffer "#chan@foonet<2>"
+        (while (accept-process-output server-process-foo))
+        (funcall expect 3 "most lively"))
+      (with-current-buffer "#chan@barnet<2>"
+        (while (accept-process-output server-process-bar))
+        (funcall expect 3 "soul black")))))
+
+(ert-deftest erc-scenarios-base-reuse-buffers-channel-buffers--disabled ()
+  :tags '(:expensive-test)
+  (should erc-reuse-buffers)
+  (let ((erc-scenarios-common-dialog "base/reuse-buffers/channel-buffers")
+        (erc-server-flood-penalty 0.1)
+        erc-reuse-buffers)
+    (erc-scenarios-common--base-reuse-buffers-server-buffers
+     #'erc-scenarios-common--base-reuse-buffers-channel-buffers)))
+
+;;; erc-scenarios-base-reuse-buffers.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-base-unstable.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-base-unstable.el
new file mode 100644
index 0000000000..4739ce9eba
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-base-unstable.el
@@ -0,0 +1,137 @@
+;;; erc-scenarios-base-unstable.el --- base unstable scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+;; Not unstable, but stashed here for now
+
+(ert-deftest erc-scenarios-aux-unix-socket ()
+  :tags '(:expensive-test)
+  (skip-unless (featurep 'make-network-process '(:family local)))
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/renick/self")
+       (erc-d-linger-secs 0.1)
+       (erc-server-flood-penalty 0.1)
+       (sock (expand-file-name "erc-d.sock" temporary-file-directory))
+       (erc-scenarios-common-extra-teardown (lambda ()
+                                              (delete-file sock)))
+       (erc-server-connect-function
+        (lambda (n b _ p &rest r)
+          (apply #'make-network-process
+                 `(:name ,n :buffer ,b :service ,p :family local ,@r))))
+       (dumb-server (erc-d-run nil sock 'auto))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "fake"
+                                       :port sock
+                                       :nick "tester"
+                                       :password "foonet:changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "fake:%s" sock)))))
+
+    (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "foonet"))
+      (erc-d-t-search-for 10 "Your new nickname is dummy"))
+
+    (ert-info ("Joined by bouncer to #foo, own nick present")
+      (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#foo"))
+        (erc-d-t-search-for 10 "dummy")
+        (erc-d-t-search-for 10 "On Thursday")))))
+
+;; See `erc-networks--rename-server-buffer'.  A perceived loss in
+;; network connectivity turns out to be a false alarm, but the bouncer
+;; has already accepted the second connection
+
+(defun erc-scenarios--base-aborted-reconnect ()
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/reconnect")
+       (erc-d-t-cleanup-sleep-secs 1)
+       (erc-d-linger-secs 0.5)
+       (dumb-server (erc-d-run "localhost" t 'aborted 'aborted-dupe))
+       (port (process-contact dumb-server :service))
+       erc-autojoin-channels-alist
+       erc-server-buffer-foo)
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "changeme"
+                                       :full-name "tester"))
+      (with-current-buffer erc-server-buffer-foo
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (ert-info ("Server buffer is unique and temp name is absent")
+      (erc-d-t-wait-for 1 (get-buffer "FooNet"))
+      (should-not (erc-scenarios-common-buflist "127.0.0.1"))
+      (with-current-buffer erc-server-buffer-foo
+        (erc-cmd-JOIN "#chan")))
+
+    (ert-info ("Channel buffer #chan alive and well")
+      (with-current-buffer (erc-d-t-wait-for 4 (get-buffer "#chan"))
+        (erc-d-t-search-for 10 "welcome")))
+
+    (ert-info ("Connect to foonet again")
+      (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                       :port port
+                                       :nick "tester"
+                                       :password "changeme"
+                                       :full-name "tester"))
+      (let ((inhibit-message noninteractive))
+        (with-current-buffer erc-server-buffer-foo
+          (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+          (erc-d-t-wait-for 5 (not (erc-server-process-alive)))
+          (erc-d-t-search-for 10 "FooNet still connected"))))
+
+    (ert-info ("Server buffer is unique and temp name is absent")
+      (should (equal (list (get-buffer "FooNet"))
+                     (erc-scenarios-common-buflist "FooNet")))
+      (should (equal (list (get-buffer (format "127.0.0.1:%d" port)))
+                     (erc-scenarios-common-buflist "127.0.0.1"))))
+
+    (ert-info ("Channel buffer #chan still going")
+      (with-current-buffer "#chan"
+        (erc-d-t-search-for 10 "and be prosperous")))))
+
+(ert-deftest erc-scenarios-base-aborted-reconnect ()
+  :tags '(:unstable)
+  (let ((tries 3)
+        (timeout 1)
+        failed)
+    (while (condition-case _err
+               (progn
+                 (erc-scenarios--base-aborted-reconnect)
+                 nil)
+             (ert-test-failed
+              (message "Test %S failed; %s attempt(s) remaining."
+                       (ert-test-name (ert-running-test))
+                       tries)
+              (sleep-for (cl-incf timeout))
+              (not (setq failed (zerop (cl-decf tries)))))))
+    (should-not failed)))
+
+;;; erc-scenarios-base-unstable.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd-id.el
new file mode 100644
index 0000000000..8b8166f8b1
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd-id.el
@@ -0,0 +1,50 @@
+;;; erc-scenarios-join-netid-newcmd-id.el --- join netid newcmd scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-join-netid--newcmd-id ()
+  :tags '(:expensive-test)
+  (let ((connect (lambda ()
+                   (erc :server "127.0.0.1"
+                        :port (with-current-buffer "oofnet"
+                                (process-contact erc-server-process :service))
+                        :nick "tester"
+                        :password "foonet:changeme"
+                        :full-name "tester"
+                        :id 'oofnet))))
+    (erc-scenarios-common--join-network-id connect 'oofnet nil)))
+
+(ert-deftest erc-scenarios-join-netid--newcmd-ids ()
+  :tags '(:expensive-test)
+  (let ((connect (lambda ()
+                   (erc :server "127.0.0.1"
+                        :port (with-current-buffer "oofnet"
+                                (process-contact erc-server-process :service))
+                        :nick "tester"
+                        :password "foonet:changeme"
+                        :full-name "tester"
+                        :id 'oofnet))))
+    (erc-scenarios-common--join-network-id connect 'oofnet 'rabnet)))
+
+;;; erc-scenarios-join-netid-newcmd-id.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd.el
new file mode 100644
index 0000000000..2eac8cd523
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-newcmd.el
@@ -0,0 +1,37 @@
+;;; erc-scenarios-join-netid-newcmd.el --- join netid newcmd scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-join-netid--newcmd ()
+  :tags '(:expensive-test)
+  (let ((connect (lambda ()
+                   (erc :server "127.0.0.1"
+                        :port (with-current-buffer "foonet"
+                                (process-contact erc-server-process :service))
+                        :nick "tester"
+                        :password "foonet:changeme"
+                        :full-name "tester"))))
+    (erc-scenarios-common--join-network-id connect nil nil)))
+
+;;; erc-scenarios-join-netid-newcmd.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon-id.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon-id.el
new file mode 100644
index 0000000000..14ca1054b3
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon-id.el
@@ -0,0 +1,46 @@
+;;; erc-scenarios-join-netid-recon-id.el --- join-netid-recon scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-join-netid--recon-id ()
+  :tags '(:expensive-test)
+  (let ((connect (lambda ()
+                   (with-current-buffer "oofnet"
+                     (erc-cmd-RECONNECT)
+                     (should (eq (current-buffer)
+                                 (process-buffer erc-server-process)))
+                     (current-buffer)))))
+    (erc-scenarios-common--join-network-id connect 'oofnet nil)))
+
+(ert-deftest erc-scenarios-join-netid--recon-ids ()
+  :tags '(:expensive-test)
+  (let ((connect (lambda ()
+                   (with-current-buffer "oofnet"
+                     (erc-cmd-RECONNECT)
+                     (should (eq (current-buffer)
+                                 (process-buffer erc-server-process)))
+                     (current-buffer)))))
+    (erc-scenarios-common--join-network-id connect 'oofnet 'rabnet)))
+
+;;; erc-scenarios-join-netid-recon-id.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon.el
new file mode 100644
index 0000000000..be5230ef4a
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-join-netid-recon.el
@@ -0,0 +1,36 @@
+;;; erc-scenarios-join-netid-recon.el --- join-netid-recon scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-join-netid--recon ()
+  :tags '(:expensive-test)
+  (let ((connect (lambda ()
+                   (with-current-buffer "foonet"
+                     (erc-cmd-RECONNECT)
+                     (should (eq (current-buffer)
+                                 (process-buffer erc-server-process)))
+                     (current-buffer)))))
+    (erc-scenarios-common--join-network-id connect nil nil)))
+
+;;; erc-scenarios-join-netid-recon.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-misc.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-misc.el
new file mode 100644
index 0000000000..22c6ac4fef
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-misc.el
@@ -0,0 +1,145 @@
+;;; erc-scenarios-misc.el --- Misc scenarios for ERC -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+(ert-deftest erc-scenarios-base-flood ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/flood")
+       (erc-d-linger-secs 0.8)
+       (dumb-server (erc-d-run "localhost" t 'soju))
+       (port (process-contact dumb-server :service))
+       (erc-server-flood-penalty 0.5) ; this ratio MUST match
+       (erc-server-flood-margin 1.5) ;  the default of 3:10
+       (expect (erc-d-t-make-expecter))
+       erc-autojoin-channels-alist)
+
+    (ert-info ("Connect to bouncer")
+      (with-current-buffer
+          (erc :server "127.0.0.1"
+               :port port
+               :nick "tester"
+               :password "changeme"
+               :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (funcall expect 5 "Soju")))
+
+    (ert-info ("#chan@foonet exists")
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan/foonet"))
+        (erc-d-t-search-for 2 "<bob/foonet>")
+        (erc-d-t-absent-for 0.1 "<joe")
+        (funcall expect 3 "was created on")))
+
+    (ert-info ("#chan@barnet exists")
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan/barnet"))
+        (erc-d-t-search-for 2 "<joe/barnet>")
+        (erc-d-t-absent-for 0.1 "<bob")
+        (funcall expect 3 "was created on")
+        (funcall expect 5 "To get good guard")))
+
+    (ert-info ("Message not held in queue limbo")
+      (with-current-buffer "#chan/foonet"
+        ;; Without 'no-penalty param in `erc-server-send', should fail
+        ;; after ~10 secs with:
+        ;;
+        ;;   (erc-d-timeout "Timed out awaiting request: (:name ~privmsg
+        ;;    :pattern \\`PRIVMSG #chan/foonet :alice: hi :timeout 2
+        ;;    :dialog soju)")
+        ;;
+        ;; Try reversing commit and spying on queue interactively
+        (erc-cmd-MSG "#chan/foonet alice: hi")
+        (funcall expect 5 "tester: Good, very good")))
+
+    (ert-info ("All output sent")
+      (with-current-buffer "#chan/foonet"
+        (funcall expect 8 "Some man or other"))
+      (with-current-buffer "#chan/barnet"
+        (while (accept-process-output erc-server-process))
+        (funcall expect 5 "That's he that was Othello")))))
+
+;; Corner case demoing fallback behavior for an absent 004 RPL but a
+;; present 422 or 375.  If this is unlikely enough, remove or guard
+;; with `ert-skip' plus some condition so it only runs when explicitly
+;; named via ERT specifier
+
+(ert-deftest erc-scenarios-networks-announced-missing ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "networks/announced-missing")
+       (erc-d-linger-secs 0.5)
+       (expect (erc-d-t-make-expecter))
+       (dumb-server (erc-d-run "localhost" t 'foonet))
+       (port (process-contact dumb-server :service)))
+
+    (ert-info ("Connect without password")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (let ((err (should-error (sleep-for 1))))
+          (should (string-match-p "Failed to determine" (cadr err))))
+        (funcall expect 1 "Failed to determine")
+        (funcall expect 1 "Failed to determine")
+        (should-not erc-network)
+        (should (string= erc-server-announced-name "irc.foonet.org"))))))
+
+;; Targets that are host/server masks like $*, $$*, and #* are routed
+;; to the server buffer: https://github.com/ircdocs/wooooms/issues/5
+
+(ert-deftest erc-scenarios-base-mask-target-routing ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/mask-target-routing")
+       (erc-d-linger-secs 0.5)
+       (dumb-server (erc-d-run "localhost" t 'foonet))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter)))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :password "changeme"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (erc-d-t-wait-for 10 (get-buffer "foonet"))
+
+    (ert-info ("Channel buffer #foo playback received")
+      (with-current-buffer (erc-d-t-wait-for 3 (get-buffer "#foo"))
+        (funcall expect 10 "Excellent workman")))
+
+    (ert-info ("Global notices routed to server buffer")
+      (with-current-buffer "foonet"
+        (funcall expect 10 "going down soon")
+        (funcall expect 10 "this is a warning")
+        (funcall expect 10 "second warning")
+        (funcall expect 10 "final warning")))
+
+    (should-not (get-buffer "$*"))))
+
+;;; erc-scenarios-misc.el ends here
diff --git a/test/lisp/erc/erc-scenarios/erc-scenarios-services-misc.el 
b/test/lisp/erc/erc-scenarios/erc-scenarios-services-misc.el
new file mode 100644
index 0000000000..07c8f1ef7a
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/erc-scenarios-services-misc.el
@@ -0,0 +1,86 @@
+;;; erc-scenarios-services-misc.el --- Services-misc scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join)
+                   (require 'erc-services))
+
+(ert-deftest erc-scenarios-services-password ()
+  :tags '(:expensive-test)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "services/password")
+       (erc-server-flood-penalty 0.1)
+       (erc-modules (cons 'services erc-modules))
+       (erc-nickserv-passwords '((Libera.Chat (("joe" . "bar")
+                                               ("tester" . "changeme")))))
+       (expect (erc-d-t-make-expecter))
+       (dumb-server (erc-d-run "localhost" t 'libera))
+       (port (process-contact dumb-server :service)))
+
+    (ert-info ("Connect without password")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (erc-d-t-wait-for 5 (eq erc-network 'Libera.Chat))
+        (funcall expect 5 "This nickname is registered.")
+        (funcall expect 2 "You are now identified")
+        (funcall expect 1 "Last login from")
+        (erc-cmd-QUIT "")))
+
+    (erc-services-mode -1)
+
+    (should-not (memq 'services erc-modules))))
+
+(ert-deftest erc-scenarios-services-prompt ()
+  :tags '(:expensive-test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "services/password")
+       (erc-server-flood-penalty 0.1)
+       (inhibit-interaction nil)
+       (erc-modules (cons 'services erc-modules))
+       (expect (erc-d-t-make-expecter))
+       (dumb-server (erc-d-run "localhost" t 'libera))
+       (port (process-contact dumb-server :service)))
+
+    (ert-info ("Connect without password")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (ert-simulate-keys "changeme\r"
+          (erc-d-t-wait-for 10 (eq erc-network 'Libera.Chat))
+          (funcall expect 3 "This nickname is registered.")
+          (funcall expect 3 "You are now identified")
+          (funcall expect 3 "Last login from"))
+        (erc-cmd-QUIT "")))
+
+    (erc-services-mode -1)
+
+    (should-not (memq 'services erc-modules))))
+
+;;; erc-scenarios-services-misc.el ends here
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/bouncer-history/barnet.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/bouncer-history/barnet.eld
similarity index 99%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/bouncer-history/barnet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/bouncer-history/barnet.eld
index 9a8408ad6a..4b6ccfff38 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/bouncer-history/barnet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/bouncer-history/barnet.eld
@@ -1,5 +1,5 @@
 ;; -*- mode: lisp-data; -*-
-((pass 2 "PASS :barnet:changeme"))
+((pass 3 "PASS :barnet:changeme"))
 ((nick 1 "NICK tester"))
 ((user 1 "USER user 0 * :tester")
  (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/bouncer-history/foonet.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/bouncer-history/foonet.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/bouncer-history/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/bouncer-history/foonet.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/multi-net/barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/association/multi-net/barnet.eld
similarity index 99%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/multi-net/barnet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/multi-net/barnet.eld
index 9aa2f2821c..c62a22a11c 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/multi-net/barnet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/multi-net/barnet.eld
@@ -18,7 +18,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 8 "MODE tester +i")
  (0 ":irc.barnet.org 221 tester +i")
  (0 ":irc.barnet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/multi-net/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/association/multi-net/foonet.eld
similarity index 99%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/multi-net/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/multi-net/foonet.eld
index 79661a0fd2..f30b7deca1 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/multi-net/foonet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/multi-net/foonet.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 8 "MODE tester +i")
  (0 ":irc.foonet.org 221 tester +i")
  (0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked-again.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked-again.eld
similarity index 97%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked-again.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked-again.eld
index c533d19dc1..ab3c7b0621 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked-again.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked-again.eld
@@ -4,7 +4,7 @@
  (0.0 ":irc.foonet.org 433 * tester :Nickname is reserved by a different 
account")
  (0.0 ":irc.foonet.org FAIL NICK NICKNAME_RESERVED tester :Nickname is 
reserved by a different account"))
 
-((nick 1 "NICK tester`")
+((nick 3 "NICK tester`")
  (0.1 ":irc.foonet.org 001 tester` :Welcome to the foonet IRC Network tester`")
  (0.0 ":irc.foonet.org 002 tester` :Your host is irc.foonet.org, running 
version oragono-2.6.1-937b9b02368748e5")
  (0.0 ":irc.foonet.org 003 tester` :This server was created Fri, 24 Sep 2021 
01:38:36 UTC")
@@ -21,7 +21,7 @@
  (0.2 ":irc.foonet.org 266 tester` 3 3 :Current global users 3, max 3")
  (0.0 ":irc.foonet.org 422 tester` :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester` +i")
+((mode-user 3.2 "MODE tester` +i")
  (0.0 ":irc.foonet.org 221 tester` +i")
  (0.0 ":irc.foonet.org NOTICE tester` :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked-foisted-again.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked-foisted-again.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked-foisted-again.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked-foisted-again.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked-foisted.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked-foisted.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked-foisted.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked-foisted.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked.eld 
b/test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked.eld
index c4aff9db5f..4e96fd7304 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/nick-bump/renicked.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/nick-bump/renicked.eld
@@ -17,7 +17,7 @@
  (0.0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0.0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 12 "MODE tester +i")
  (0.0 ":irc.foonet.org 221 tester +i")
  (0.0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/reconnect-playback/foonet-again.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/reconnect-playback/foonet-again.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/reconnect-playback/foonet-again.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/reconnect-playback/foonet-again.eld
index 1eb633260c..4210c07e41 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/reconnect-playback/foonet-again.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/reconnect-playback/foonet-again.eld
@@ -17,7 +17,7 @@
  (0.0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0.0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10 "MODE tester +i")
  ;; No mode answer
  (0.0 ":tester!~u@mw6kegwt77kwe.irc JOIN #chan")
  (0.0 ":irc.foonet.org 353 tester = #chan :alice @bob tester")
@@ -27,7 +27,7 @@
  (0.0 ":bob!~u@mw6kegwt77kwe.irc PRIVMSG #chan :[10:37:56] alice: With these 
mortals on the ground.")
  (0.0 ":***!znc@znc.in PRIVMSG #chan :Playback Complete."))
 
-((mode 1 "MODE #chan")
+((mode 10 "MODE #chan")
  (0.0 ":irc.foonet.org 324 tester #chan +nt")
  (0.0 ":irc.foonet.org 329 tester #chan 1623816901")
  (0.1 ":bob!~u@mw6kegwt77kwe.irc PRIVMSG #chan :alice: My name, my good lord, 
is Parolles.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/reconnect-playback/foonet.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/reconnect-playback/foonet.eld
similarity index 97%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/reconnect-playback/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/reconnect-playback/foonet.eld
index 347e565498..6f50ecca4e 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/reconnect-playback/foonet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/reconnect-playback/foonet.eld
@@ -17,7 +17,7 @@
  (0.0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0.0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 5 "MODE tester +i")
  ;; No mode answer
  (0.0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0.0 ":tester!~u@mw6kegwt77kwe.irc JOIN #chan")
@@ -33,7 +33,7 @@
  (0.0 ":***!znc@znc.in PRIVMSG #chan :Playback Complete.")
  (0.0 ":irc.foonet.org 305 tester :You are no longer marked as being away"))
 
-((mode 1 "MODE #chan")
+((mode 3 "MODE #chan")
  (1.0 ":irc.foonet.org 324 tester #chan +nt")
  (0.0 ":irc.foonet.org 329 tester #chan 1623816901")
  (0.1 ":alice!~u@mw6kegwt77kwe.irc PRIVMSG #chan :bob: At thy good heart's 
oppression.")
@@ -47,6 +47,6 @@
  (0.1 ":alice!~u@mw6kegwt77kwe.irc PRIVMSG #chan :bob: And at my suit, sweet, 
pardon what is past.")
  (0.1 ":bob!~u@mw6kegwt77kwe.irc PRIVMSG #chan :alice: My lord, you give me 
most egregious indignity."))
 
-((quit 1 "QUIT :\2ERC\2"))
+((quit 2 "QUIT :\2ERC\2"))
 
 ((drop 0 DROP))
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/same-network/chester.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/same-network/chester.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/same-network/chester.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/same-network/chester.eld
index e51cc590b0..f1aed2836c 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/same-network/chester.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/same-network/chester.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 chester 3 4 :Current global users 3, max 4")
  (0 ":irc.foonet.org 422 chester :MOTD File is missing"))
 
-((mode-user 2.2 "MODE chester +i")
+((mode-user 12 "MODE chester +i")
  (0 ":irc.foonet.org 221 chester +i")
  (0 ":chester!~u@yuvqisyu7m7qs.irc JOIN #chan")
  (0 ":irc.foonet.org 353 chester = #chan :tester chester @alice bob")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/same-network/tester-again.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/same-network/tester-again.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/same-network/tester-again.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/same-network/tester-again.eld
index 1fb0a63ad6..67c3a94a26 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/same-network/tester-again.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/same-network/tester-again.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 tester 4 4 :Current global users 4, max 4")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 2.2 "MODE tester +i")
+((mode-user 4.2 "MODE tester +i")
  (0 ":irc.foonet.org 221 tester +i")
  (0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect.")
  (0 ":tester!~u@yuvqisyu7m7qs.irc JOIN #chan")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/association/same-network/tester.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/association/same-network/tester.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/association/same-network/tester.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/association/same-network/tester.eld
index 333658fe94..cd9cacbe5d 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/association/same-network/tester.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/association/same-network/tester.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 tester 4 4 :Current global users 4, max 4")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 2.2 "MODE tester +i")
+((mode-user 12 "MODE tester +i")
  (0 ":irc.foonet.org 221 tester +i")
  (0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git a/test/lisp/erc/erc-scenarios-resources/base/auth-source/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/auth-source/foonet.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/auth-source/foonet.eld
rename to test/lisp/erc/erc-scenarios/resources/base/auth-source/foonet.eld
diff --git a/test/lisp/erc/erc-scenarios-resources/base/auth-source/nopass.eld 
b/test/lisp/erc/erc-scenarios/resources/base/auth-source/nopass.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/auth-source/nopass.eld
rename to test/lisp/erc/erc-scenarios/resources/base/auth-source/nopass.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/channel-buffer-revival/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/channel-buffer-revival/foonet.eld
similarity index 99%
rename from 
test/lisp/erc/erc-scenarios-resources/base/channel-buffer-revival/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/channel-buffer-revival/foonet.eld
index cc719d275f..b09692327c 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/channel-buffer-revival/foonet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/channel-buffer-revival/foonet.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 3.2 "MODE tester +i")
+((mode-user 12 "MODE tester +i")
  (0 ":irc.foonet.org 221 tester +i")
  (0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git a/test/lisp/erc/erc-scenarios-resources/base/flood/soju.eld 
b/test/lisp/erc/erc-scenarios/resources/base/flood/soju.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/flood/soju.eld
rename to test/lisp/erc/erc-scenarios/resources/base/flood/soju.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/gapless-connect/barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/gapless-connect/barnet.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/gapless-connect/barnet.eld
rename to test/lisp/erc/erc-scenarios/resources/base/gapless-connect/barnet.eld
index a819e81775..4e658802ef 100644
--- a/test/lisp/erc/erc-scenarios-resources/base/gapless-connect/barnet.eld
+++ b/test/lisp/erc/erc-scenarios/resources/base/gapless-connect/barnet.eld
@@ -16,7 +16,7 @@
  (0 ":irc.barnet.org 266 tester 1 1 :Current global users 1, max 1")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 3.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@8cgjyczyrjgby.irc JOIN #bar")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/gapless-connect/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/gapless-connect/foonet.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/gapless-connect/foonet.eld
rename to test/lisp/erc/erc-scenarios/resources/base/gapless-connect/foonet.eld
index dc76a7307f..4ac4a3e596 100644
--- a/test/lisp/erc/erc-scenarios-resources/base/gapless-connect/foonet.eld
+++ b/test/lisp/erc/erc-scenarios/resources/base/gapless-connect/foonet.eld
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 3.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@xrir8fpe4d7ak.irc JOIN #foo")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/gapless-connect/pass-stub.eld 
b/test/lisp/erc/erc-scenarios/resources/base/gapless-connect/pass-stub.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/gapless-connect/pass-stub.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/gapless-connect/pass-stub.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/mask-target-routing/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/mask-target-routing/foonet.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/mask-target-routing/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/mask-target-routing/foonet.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet-again.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet-again.eld
similarity index 95%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet-again.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet-again.eld
index 62d17692cf..69ca50aab9 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet-again.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet-again.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 1 "PASS :barnet:changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((pass 3 "PASS :barnet:changeme"))
+((nick 3 "NICK tester"))
+((user 3 "USER user 0 * :tester")
  (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
  (0 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.barnet.org 003 tester :This server was created Wed, 12 May 2021 
07:41:08 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 2.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer ^
 
  (0 ":tester!~u@xrir8fpe4d7ak.irc JOIN #chan")
@@ -38,7 +38,7 @@
 
 ((~join 3 "JOIN #chan"))
 
-((mode 2 "MODE #chan")
+((mode 5 "MODE #chan")
  (0 ":irc.barnet.org 324 tester #chan +nt")
  (0 ":irc.barnet.org 329 tester #chan 1620805269")
  (0.1 ":joe!~u@svpn88yjcdj42.irc PRIVMSG #chan :mike: But, in defence, by 
mercy, 'tis most just.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet-drop.eld 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet-drop.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet-drop.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet-drop.eld
index 9b5edd6208..dff88f3a8d 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet-drop.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet-drop.eld
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer ^
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.barnet.org 305 tester :You are no longer marked as being away"))
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet.eld
similarity index 94%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet.eld
index 720e7cf8c8..abfcc6ed48 100644
--- a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/barnet.eld
+++ b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/barnet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 1 "PASS :barnet:changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((pass 3 "PASS :barnet:changeme"))
+((nick 3 "NICK tester"))
+((user 3 "USER user 0 * :tester")
  (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
  (0 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.barnet.org 003 tester :This server was created Wed, 12 May 2021 
07:41:08 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer ^
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.barnet.org 305 tester :You are no longer marked as being away"))
@@ -29,7 +29,7 @@
  (0.1 ":joe!~u@awyxgybtkx7uq.irc PRIVMSG #chan :tester, welcome!")
  (0 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :tester, welcome!"))
 
-((mode 1 "MODE #chan")
+((mode 3 "MODE #chan")
  (0 ":irc.barnet.org 324 tester #chan +nt")
  (0 ":irc.barnet.org 329 tester #chan 1620805269")
  (0.1 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :joe: But you have outfaced 
them all.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet-again.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet-again.eld
similarity index 97%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet-again.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet-again.eld
index b99beafc4b..c74dcb585f 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet-again.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet-again.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
 ((pass 3 "PASS :foonet:changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((nick 3 "NICK tester"))
+((user 3 "USER user 0 * :tester")
  (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
  (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.foonet.org 003 tester :This server was created Wed, 12 May 2021 
07:41:09 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 2.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer ^
  (0 ":tester!~u@nvfhxvqm92rm6.irc JOIN #chan")
  (0 ":irc.foonet.org 353 tester = #chan :alice @bob tester")
@@ -38,7 +38,7 @@
 
 ((~join 3 "JOIN #chan"))
 
-((mode 3 "MODE #chan")
+((mode 8 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620805271")
  (0.1 ":alice!~u@svpn88yjcdj42.irc PRIVMSG #chan :bob: Grows, lives, and dies, 
in single blessedness.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet-drop.eld 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet-drop.eld
similarity index 99%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet-drop.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet-drop.eld
index 630742603e..e3c41e2133 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet-drop.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet-drop.eld
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer ^
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.foonet.org 305 tester :You are no longer marked as being away"))
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet.eld
similarity index 95%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet.eld
index 4bbef6abc7..c241c59bb8 100644
--- a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/foonet.eld
+++ b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/foonet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 1 "PASS :foonet:changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((pass 3 "PASS :foonet:changeme"))
+((nick 3 "NICK tester"))
+((user 3 "USER user 0 * :tester")
  (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
  (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.foonet.org 003 tester :This server was created Wed, 12 May 2021 
07:41:09 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 4.2 "MODE tester +i")
  ;; No mode answer ^
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.foonet.org 305 tester :You are no longer marked as being away"))
@@ -29,7 +29,7 @@
  (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :tester, welcome!")
  (0 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :tester, welcome!"))
 
-((mode 1 "MODE #chan")
+((mode 3 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620805271")
  (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: He cannot be heard of. 
Out of doubt he is transported.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/stub-again.eld 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/stub-again.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/bouncer/stub-again.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/bouncer/stub-again.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/same-network/chester.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/same-network/chester.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/same-network/chester.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/same-network/chester.eld
index 2cdc1f263f..8c2448733c 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/same-network/chester.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/same-network/chester.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 chester 3 4 :Current global users 3, max 4")
  (0 ":irc.foonet.org 422 chester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE chester +i")
+((mode-user 10.2 "MODE chester +i")
  (0 ":irc.foonet.org 221 chester +i")
  (0 ":irc.foonet.org NOTICE chester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/same-network/tester.eld 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/same-network/tester.eld
similarity index 99%
rename from 
test/lisp/erc/erc-scenarios-resources/base/network-id/same-network/tester.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/network-id/same-network/tester.eld
index 38e505a101..76312a7a14 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/network-id/same-network/tester.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/network-id/same-network/tester.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 tester 4 4 :Current global users 4, max 4")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  (0 ":irc.foonet.org 221 tester +i")
  (0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is 
logging all user I/O. If you do not wish for everything you send to be readable 
by the server owner(s), please disconnect."))
 
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/reconnect/aborted-dupe.eld 
b/test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted-dupe.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/reconnect/aborted-dupe.eld
rename to test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted-dupe.eld
diff --git a/test/lisp/erc/erc-scenarios-resources/base/reconnect/aborted.eld 
b/test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/reconnect/aborted.eld
rename to test/lisp/erc/erc-scenarios/resources/base/reconnect/aborted.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/reconnect/options-again.eld 
b/test/lisp/erc/erc-scenarios/resources/base/reconnect/options-again.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/reconnect/options-again.eld
rename to test/lisp/erc/erc-scenarios/resources/base/reconnect/options-again.eld
diff --git a/test/lisp/erc/erc-scenarios-resources/base/reconnect/options.eld 
b/test/lisp/erc/erc-scenarios/resources/base/reconnect/options.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/reconnect/options.eld
rename to test/lisp/erc/erc-scenarios/resources/base/reconnect/options.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/reconnect/timer-last.eld 
b/test/lisp/erc/erc-scenarios/resources/base/reconnect/timer-last.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/reconnect/timer-last.eld
rename to test/lisp/erc/erc-scenarios/resources/base/reconnect/timer-last.eld
diff --git a/test/lisp/erc/erc-scenarios-resources/base/reconnect/timer.eld 
b/test/lisp/erc/erc-scenarios/resources/base/reconnect/timer.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/reconnect/timer.eld
rename to test/lisp/erc/erc-scenarios/resources/base/reconnect/timer.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/renick/queries/bouncer-barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/renick/queries/bouncer-barnet.eld
similarity index 96%
rename from 
test/lisp/erc/erc-scenarios-resources/base/renick/queries/bouncer-barnet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/renick/queries/bouncer-barnet.eld
index 9755920f37..fc6cdaafe9 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/renick/queries/bouncer-barnet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/renick/queries/bouncer-barnet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 1 "PASS :barnet:changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((pass 3 "PASS :barnet:changeme"))
+((nick 3 "NICK tester"))
+((user 3 "USER user 0 * :tester")
  (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
  (0 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.barnet.org 003 tester :This server was created Tue, 01 Jun 2021 
07:49:23 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 3.2 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@286u8jcpis84e.irc JOIN #chan")
@@ -32,7 +32,7 @@
  (0 ":irc.barnet.org NOTICE tester :[09:13:24] This server is in debug mode 
and is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
  (0 ":irc.barnet.org 305 tester :You are no longer marked as being away"))
 
-((mode 1 "MODE #chan")
+((mode 5 "MODE #chan")
  (0 ":irc.barnet.org 324 tester #chan +nt")
  (0 ":irc.barnet.org 329 tester #chan 1622538742")
  (0.1 ":joe!~u@286u8jcpis84e.irc PRIVMSG #chan :mike: By favours several which 
they did bestow.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/renick/queries/bouncer-foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/base/renick/queries/bouncer-foonet.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/renick/queries/bouncer-foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/renick/queries/bouncer-foonet.eld
index 0af67935a5..2da538afd9 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/renick/queries/bouncer-foonet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/renick/queries/bouncer-foonet.eld
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 5.2 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@u4mvbswyw8gbg.irc JOIN #chan")
@@ -32,7 +32,7 @@
  (0 ":irc.foonet.org NOTICE tester :[09:12:53] This server is in debug mode 
and is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
  (0 ":irc.foonet.org 305 tester :You are no longer marked as being away"))
 
-((mode 1 "MODE #chan")
+((mode 10 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1622538742")
  (0.1 ":bob!~u@u4mvbswyw8gbg.irc PRIVMSG #chan :alice: When there is nothing 
living but thee, thou shalt be welcome. I had rather be a beggar's dog than 
Apemantus.")
diff --git a/test/lisp/erc/erc-scenarios-resources/base/renick/queries/solo.eld 
b/test/lisp/erc/erc-scenarios/resources/base/renick/queries/solo.eld
similarity index 99%
rename from test/lisp/erc/erc-scenarios-resources/base/renick/queries/solo.eld
rename to test/lisp/erc/erc-scenarios/resources/base/renick/queries/solo.eld
index b3189871aa..12fa7d264e 100644
--- a/test/lisp/erc/erc-scenarios-resources/base/renick/queries/solo.eld
+++ b/test/lisp/erc/erc-scenarios/resources/base/renick/queries/solo.eld
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 4 4 :Current global users 4, max 4")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 8 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@gq7yjr7gsu7nn.irc JOIN #foo")
diff --git a/test/lisp/erc/erc-scenarios-resources/base/renick/self/auto.eld 
b/test/lisp/erc/erc-scenarios/resources/base/renick/self/auto.eld
similarity index 98%
rename from test/lisp/erc/erc-scenarios-resources/base/renick/self/auto.eld
rename to test/lisp/erc/erc-scenarios/resources/base/renick/self/auto.eld
index 5b9c26738d..851db7f1cf 100644
--- a/test/lisp/erc/erc-scenarios-resources/base/renick/self/auto.eld
+++ b/test/lisp/erc/erc-scenarios/resources/base/renick/self/auto.eld
@@ -25,7 +25,7 @@
  (0 ":irc.foonet.org 372 dummy :- Please visit us in #libera for questions and 
support.")
  (0 ":irc.foonet.org 376 dummy :End of /MOTD command."))
 
-((mode-user 1.2 "MODE dummy +i")
+((mode-user 10.2 "MODE dummy +i")
  (0 ":dummy!~u@gq7yjr7gsu7nn.irc MODE dummy :+RZi")
  (0 ":irc.znc.in 306 dummy :You have been marked as being away")
  (0 ":dummy!~u@gq7yjr7gsu7nn.irc JOIN #foo")
@@ -39,7 +39,7 @@
  (0 ":irc.foonet.org NOTICE dummy :[09:56:57] This server is in debug mode and 
is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
  (0 ":irc.foonet.org 305 dummy :You are no longer marked as being away"))
 
-((mode 1 "MODE #foo")
+((mode 10 "MODE #foo")
  (0 ":irc.foonet.org 324 dummy #foo +nt")
  (0 ":irc.foonet.org 329 dummy #foo 1622454985")
  (0.1 ":alice!~u@gq7yjr7gsu7nn.irc PRIVMSG #foo :bob: Farewell, pretty lady: 
you must hold the credit of your father.")
diff --git a/test/lisp/erc/erc-scenarios-resources/base/renick/self/manual.eld 
b/test/lisp/erc/erc-scenarios/resources/base/renick/self/manual.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/base/renick/self/manual.eld
rename to test/lisp/erc/erc-scenarios/resources/base/renick/self/manual.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/renick/self/qual-chester.eld 
b/test/lisp/erc/erc-scenarios/resources/base/renick/self/qual-chester.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/renick/self/qual-chester.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/renick/self/qual-chester.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/renick/self/qual-tester.eld 
b/test/lisp/erc/erc-scenarios/resources/base/renick/self/qual-tester.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/base/renick/self/qual-tester.eld
rename to test/lisp/erc/erc-scenarios/resources/base/renick/self/qual-tester.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/channel-buffers/barnet.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/channel-buffers/barnet.eld
similarity index 97%
rename from 
test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/channel-buffers/barnet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/channel-buffers/barnet.eld
index c90c399aed..82700c5912 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/channel-buffers/barnet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/channel-buffers/barnet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 1 "PASS :barnet:changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((pass 3 "PASS :barnet:changeme"))
+((nick 3 "NICK tester"))
+((user 3 "USER user 0 * :tester")
  (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
  (0 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.barnet.org 003 tester :This server was created Wed, 05 May 2021 
09:05:33 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@wvys46tx8tpmk.irc JOIN #chan")
@@ -34,7 +34,7 @@
  (0 ":irc.barnet.org NOTICE tester :[09:05:35] This server is in debug mode 
and is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
  (0 ":irc.barnet.org 305 tester :You are no longer marked as being away"))
 
-((mode 1 "MODE #chan")
+((mode 3 "MODE #chan")
  (0 ":irc.barnet.org 324 tester #chan +nt")
  (0 ":irc.barnet.org 329 tester #chan 1620205534")
  (0.1 ":mike!~u@wvys46tx8tpmk.irc PRIVMSG #chan :joe: That will be given to 
the loudest noise we make.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/channel-buffers/foonet.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/channel-buffers/foonet.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/channel-buffers/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/channel-buffers/foonet.eld
index 648321875b..a11cfac2e7 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/channel-buffers/foonet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/channel-buffers/foonet.eld
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 12 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@247eaxkrufj44.irc JOIN #chan")
@@ -34,7 +34,7 @@
  (0 ":irc.foonet.org NOTICE tester :[09:06:05] This server is in debug mode 
and is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
  (0 ":irc.foonet.org 305 tester :You are no longer marked as being away"))
 
-((mode 1 "MODE #chan")
+((mode 10 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620205534")
  (0.5 ":bob!~u@yppdd5tt4admc.irc PRIVMSG #chan :alice: Nor I no strength to 
climb without thy help.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/server-buffers/barnet.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/server-buffers/barnet.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/server-buffers/barnet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/server-buffers/barnet.eld
index 2c4264c746..cc7aff1007 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/server-buffers/barnet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/server-buffers/barnet.eld
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.barnet.org NOTICE tester :[11:29:00] This server is in debug mode 
and is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/server-buffers/foonet.eld
 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/server-buffers/foonet.eld
similarity index 98%
rename from 
test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/server-buffers/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/server-buffers/foonet.eld
index 2a8418eecf..3a84610846 100644
--- 
a/test/lisp/erc/erc-scenarios-resources/base/reuse-buffers/server-buffers/foonet.eld
+++ 
b/test/lisp/erc/erc-scenarios/resources/base/reuse-buffers/server-buffers/foonet.eld
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.foonet.org NOTICE tester :[11:29:00] This server is in debug mode 
and is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
diff --git a/test/lisp/erc/erc-d/erc-d-i.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-i.el
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-i.el
rename to test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-i.el
diff --git a/test/lisp/erc/erc-d/erc-d-t.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-t.el
similarity index 97%
rename from test/lisp/erc/erc-d/erc-d-t.el
rename to test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-t.el
index 97231a3755..eebb0f1e2c 100644
--- a/test/lisp/erc/erc-d/erc-d-t.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-t.el
@@ -21,10 +21,11 @@
 ;;; Commentary:
 
 ;;; Code:
-(eval-and-compile (let ((dir (getenv "EMACS_TEST_DIRECTORY")))
-                    (when dir
-                      (load (concat dir "/lisp/erc/erc-d/erc-d-u") nil t))))
-(require 'erc-d-u)
+(eval-and-compile
+  (let* ((d (file-name-directory (or (macroexp-file-name) buffer-file-name)))
+         (load-path (cons (directory-file-name d) load-path)))
+    (require 'erc-d-u)))
+
 (require 'ert)
 
 (defun erc-d-t-kill-related-buffers ()
diff --git a/test/lisp/erc/erc-d/erc-d-u.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-u.el
similarity index 99%
rename from test/lisp/erc/erc-d/erc-d-u.el
rename to test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-u.el
index 187ee272d1..0c834c8714 100644
--- a/test/lisp/erc/erc-d/erc-d-u.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d-u.el
@@ -109,7 +109,7 @@
 
 (defvar erc-d-u--library-directory (file-name-directory load-file-name))
 (defvar erc-d-u-canned-dialog-dir
-  (file-name-as-directory (expand-file-name "erc-d-self-resources"
+  (file-name-as-directory (expand-file-name "resources"
                                             erc-d-u--library-directory)))
 
 (defun erc-d-u--normalize-canned-name (dialog)
diff --git a/test/lisp/erc/erc-d/erc-d.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d.el
similarity index 98%
rename from test/lisp/erc/erc-d/erc-d.el
rename to test/lisp/erc/erc-scenarios/resources/erc-d/erc-d.el
index b84e414548..857f6399f9 100644
--- a/test/lisp/erc/erc-d/erc-d.el
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/erc-d.el
@@ -68,7 +68,7 @@
 ;; flexibility.  However, it's best to keep things simple (even if
 ;; overly verbose), so others can easily tell what's going on at a
 ;; glance.  If necessary, consult existing tests for examples (grep
-;; for the variables `erc-d-spec-vars' and `erc-d-match-handlers').
+;; for the variables `erc-d-tmpl-vars' and `erc-d-match-handlers').
 ;;
 ;; Subprocess or in-process?:
 ;;
@@ -105,7 +105,7 @@
 ;;
 ;; * vars (alist of cons pairs); for sharing state among template
 ;; functions during the lifetime of an exchange.  Initially populated
-;; by `erc-d-spec-vars', these KEY/VALUE pairs are made available in
+;; by `erc-d-tmpl-vars', these KEY/VALUE pairs are made available in
 ;; the template environment as bound variables.  Updates can be made
 ;; by exchange handlers (see `erc-d-match-handlers').  When VALUE is a
 ;; function, occurrences of KEY in an outgoing spec are replaced with
@@ -122,13 +122,13 @@
 ;; - Maybe migrate d-u and d-i dependencies here
 
 ;;; Code:
-(eval-and-compile (let ((dir (getenv "EMACS_TEST_DIRECTORY")))
-                    (when dir
-                      (load (concat dir "/lisp/erc/erc-d/erc-d-i") nil t)
-                      (load (concat dir "/lisp/erc/erc-d/erc-d-u") nil t))))
+(eval-and-compile
+  (let* ((d (file-name-directory (or (macroexp-file-name) buffer-file-name)))
+         (load-path (cons (directory-file-name d) load-path)))
+    (require 'erc-d-i)
+    (require 'erc-d-u)))
+
 (require 'ring)
-(require 'erc-d-i)
-(require 'erc-d-u)
 
 (defvar erc-d-server-name "erc-d-server"
   "Default name of a server process and basis for its buffer name.
@@ -144,7 +144,7 @@ dialog templates for the sender portion of a reply 
message.")
 For more granular control, use the provided LINGER `rx' variable (alone)
 as the incoming template spec of a dialog's last exchange.")
 
-(defvar erc-d-spec-vars nil
+(defvar erc-d-tmpl-vars nil
   "An alist of template bindings available to client dialogs.
 Populate it when calling `erc-d-run', and the contents will be made
 available to all client dialogs through the `erc-d-dialog' \"vars\"
@@ -163,7 +163,7 @@ event that an exchange-specific handler is needed, see
 
 (defvar erc-d-match-handlers nil
   "A plist of exchange-tag symbols mapped to request-handler functions.
-This is meant to address edge cases for which `erc-d-spec-vars' comes up
+This is meant to address edge cases for which `erc-d-tmpl-vars' comes up
 short.  These may include (1) needing access to the client process
 itself and/or (2) adding or altering outgoing response templates before
 rendering.  Note that (2) requires using `erc-d-exchange-rebind' instead
@@ -918,12 +918,12 @@ process, otherwise sleep for the duration of the server 
process.
 A dialog must be a symbol matching the base name of a dialog file in
 `erc-d-u-canned-dialog-dir'.
 
-The variable `erc-d-spec-vars' determines the common members of the
+The variable `erc-d-tmpl-vars' determines the common members of the
 `erc-d--render-entries' ENTRIES param.  Variables `erc-d-server-fqdn'
 and `erc-d-linger-secs' determine the `erc-d-dialog' items
 `:server-fqdn' and `:linger-secs' for all client processes.
 
-The variable `erc-d-spec-vars' can be used to initialize the
+The variable `erc-d-tmpl-vars' can be used to initialize the
 process's `erc-d-dialog' vars item."
   (when (and server-name (symbolp server-name))
     (push server-name dialogs)
@@ -937,7 +937,7 @@ process's `erc-d-dialog' vars item."
     (setq dialogs loaded))
   (erc-d--start host service (or server-name erc-d-server-name)
                 :dialog-dialogs dialogs
-                :dialog-vars erc-d-spec-vars
+                :dialog-vars erc-d-tmpl-vars
                 :dialog-linger-secs erc-d-linger-secs
                 :dialog-server-fqdn erc-d-server-fqdn
                 :dialog-match-handlers (erc-d-u--unkeyword
@@ -951,10 +951,9 @@ outgoing messages to standard out.
 
 The main options are --host HOST and --port PORT, which default to
 localhost and auto, respectively.  The args are the dialogs to run.
-Unlike with `erc-d-run', dialogs here *must* be file paths, meaning
-Lisp-Data files adhering to the required format.  (These consist of
-\"specs\" detailing timing and template info; see commentary for
-specifics.)
+Unlike with `erc-d-run', dialogs here *must* be files, meaning Lisp-Data
+files adhering to the required format.  (These consist of \"specs\"
+detailing timing and template info; see commentary for specifics.)
 
 An optional --add-time N option can also be passed to hike up timeouts
 by some number of seconds N.  For example, you might run:
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/basic.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/basic.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/basic.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/basic.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/depleted.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/depleted.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/depleted.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/depleted.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/drop-a.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/drop-a.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/drop-a.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/drop-a.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/drop-b.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/drop-b.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/drop-b.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/drop-b.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/dynamic-barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic-barnet.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/dynamic-barnet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic-barnet.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/dynamic-foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic-foonet.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/dynamic-foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic-foonet.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/dynamic-stub.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic-stub.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/dynamic-stub.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic-stub.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/dynamic.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic.eld
similarity index 89%
rename from test/lisp/erc/erc-d/erc-d-self-resources/dynamic.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic.eld
index 8698560109..459b6e52bf 100644
--- a/test/lisp/erc/erc-d/erc-d-self-resources/dynamic.eld
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/dynamic.eld
@@ -1,8 +1,8 @@
 ;;; -*- mode: lisp-data -*-
 ((pass 10.0 "PASS " (? ?:) "changeme"))
-((nick 0.2 "NICK tester"))
+((nick 2.2 "NICK tester"))
 
-((user 0.2 "USER " user " " (ignored digit "*") " :" realname)
+((user 2.2 "USER " user " " (ignored digit "*") " :" realname)
  (0.0 ":" dom " 001 " nick " :Welcome to the Internet Relay Network tester")
  (0.0 ":" dom " 002 " nick " :Your host is " dom)
  (0.0 ":" dom " 003 " nick " :This server was created just now")
@@ -18,7 +18,7 @@
  (0.0 ":" dom " 266 " nick " 3 3 :Current global users 3, max 3")
  (0.0 ":" dom " 422 " nick " :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 2.2 "MODE tester +i")
  (0.0 ":" dom " 221 " nick " +Zi")
 
  (0.0 ":" dom " 306 " nick " :You have been marked as being away")
@@ -26,5 +26,5 @@
  (0.0 ":" dom " 353 alice = #chan :+alice!~alice@example.com 
@%+bob!~bob@example.org")
  (0.0 ":" dom " 366 alice #chan :End of NAMES list"))
 
-((mode 1.2 "MODE #chan")
+((mode 2.2 "MODE #chan")
  (0.1 ":bob!~bob@example.org PRIVMSG #chan :" nick ": hey"))
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/eof.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/eof.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/eof.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/eof.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/fuzzy.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/fuzzy.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/fuzzy.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/fuzzy.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/incremental.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/incremental.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/incremental.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/incremental.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/irc-parser-tests.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/irc-parser-tests.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/irc-parser-tests.eld
rename to 
test/lisp/erc/erc-scenarios/resources/erc-d/resources/irc-parser-tests.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/linger-multi-a.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/linger-multi-a.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/linger-multi-a.eld
rename to 
test/lisp/erc/erc-scenarios/resources/erc-d/resources/linger-multi-a.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/linger-multi-b.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/linger-multi-b.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/linger-multi-b.eld
rename to 
test/lisp/erc/erc-scenarios/resources/erc-d/resources/linger-multi-b.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/linger.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/linger.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/linger.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/linger.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/no-block.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/no-block.eld
similarity index 98%
rename from test/lisp/erc/erc-d/erc-d-self-resources/no-block.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/no-block.eld
index cd341dd192..1b1f396563 100644
--- a/test/lisp/erc/erc-d/erc-d-self-resources/no-block.eld
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/no-block.eld
@@ -52,4 +52,4 @@
 ((mode-bar 1.5 "MODE #bar")
  (0.0 ":irc.example.org 324 tester #bar +HMfnrt 50:5h :10:5")
  (0.0 ":irc.example.org 329 tester #bar :1602642829")
- (0.1 ":alice!~alice@example.com PRIVMSG #bar :hi"))
+ (0.1 ":alice!~alice@example.com PRIVMSG #bar :hi 123"))
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/no-match.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/no-match.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/no-match.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/no-match.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/no-pong.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/no-pong.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/no-pong.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/no-pong.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/nonstandard.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/nonstandard.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/nonstandard.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/nonstandard.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/proxy-barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-barnet.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/proxy-barnet.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-barnet.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/proxy-foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-foonet.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/proxy-foonet.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-foonet.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/proxy-solo.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-solo.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/proxy-solo.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-solo.eld
diff --git 
a/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-subprocess.el 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-subprocess.el
new file mode 100644
index 0000000000..fed6206a6a
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/proxy-subprocess.el
@@ -0,0 +1,45 @@
+;;; proxy-subprocess.el --- Example setup file for erc-d  -*- lexical-binding: 
t; -*-
+
+;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;; Code:
+
+(defvar erc-d-tmpl-vars)
+
+(setq erc-d-tmpl-vars
+
+      (list
+       (cons 'fqdn (lambda (helper)
+                     (let ((name (funcall helper :dialog-name)))
+                       (funcall helper :set
+                                (if (eq name 'proxy-foonet)
+                                    "irc.foo.net"
+                                  "irc.bar.net")))))
+
+       (cons 'net (lambda (helper)
+                    (let ((name (funcall helper :dialog-name)))
+                      (funcall helper :set
+                               (if (eq name 'proxy-foonet)
+                                   "FooNet"
+                                 "BarNet")))))
+
+       (cons 'network '(group (+ alpha)))))
+
+;;; proxy-subprocess.el ends here
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/timeout.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/timeout.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/timeout.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/timeout.eld
diff --git a/test/lisp/erc/erc-d/erc-d-self-resources/unexpected.eld 
b/test/lisp/erc/erc-scenarios/resources/erc-d/resources/unexpected.eld
similarity index 100%
rename from test/lisp/erc/erc-d/erc-d-self-resources/unexpected.eld
rename to test/lisp/erc/erc-scenarios/resources/erc-d/resources/unexpected.eld
diff --git a/test/lisp/erc/erc-scenarios/resources/erc-scenarios-common.el 
b/test/lisp/erc/erc-scenarios/resources/erc-scenarios-common.el
new file mode 100644
index 0000000000..0237c3e862
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios/resources/erc-scenarios-common.el
@@ -0,0 +1,467 @@
+;;; erc-scenarios-common.el --- common helpers for ERC scenarios -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; This program 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.
+;;
+;; This program 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 this program.  If not, see
+;; <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; These are e2e-ish test cases primarily intended to assert core,
+;; fundamental behavior expected of any modern IRC client.  Tests may
+;; also simulate specific scenarios drawn from bug reports.  Incoming
+;; messages are provided by playback scripts resembling I/O logs.  In
+;; place of time stamps, they have time deltas, which are used to
+;; govern the test server in a fashion reminiscent of music rolls (or
+;; the script(1) UNIX program).  These scripts can be found in the
+;; other directories under test/lisp/erc/erc-scenarios/resources.
+;;
+;; Isolation:
+;;
+;; The set of enabled modules is shared among all tests.  The function
+;; `erc-update-modules' activates them (as minor modes), but it never
+;; deactivates them.  So there's no going back, and let-binding
+;; `erc-modules' is useless.  The safest route is therefore to (1)
+;; assume the set of default modules is already activated or will be
+;; over the course of the test session and (2) let-bind relevant user
+;; options as needed.  For example, to limit the damage of
+;; `erc-autojoin-channels-alist' to a given test, assume the
+;; `erc-join' library has already been loaded or will be on the next
+;; call to `erc-open'.  And then just let-bind
+;; `erc-autojoin-channels-alist' for the duration of the test.
+;;
+;; Playing nice:
+;;
+;; Right now, these tests all rely on an ugly fixture macro named
+;; `erc-scenarios-common-with-cleanup', which is defined just below.
+;; It helps restore (but not really prepare) the environment by
+;; destroying any stray processes or buffers named in the first
+;; argument, a `let*'-style VAR-LIST.  Relying on such a macro is
+;; unfortunate because in many ways it actually hampers readability by
+;; favoring magic over verbosity.  But without it (or something
+;; similar), any failing test would cause all subsequent tests in this
+;; file to fail like dominoes (making all but the first backtrace
+;; useless).
+;;
+;; Misc:
+;;
+;; Note that in the following examples, nicknames Alice and Bob are
+;; always associated with the fake network FooNet, while nicks Joe and
+;; Mike are always on BarNet.  (Networks are sometimes downcased.)
+;;
+;; XXX This file should *not* contain any test cases.
+
+;;; Code:
+
+(require 'ert-x) ; cl-lib
+(eval-and-compile
+  (let* ((d (expand-file-name ".." (ert-resource-directory)))
+         (load-path (cons (concat d "/erc-d") load-path)))
+    (require 'erc-d-t)
+    (require 'erc-d)))
+
+(require 'erc-backend)
+
+(eval-when-compile (require 'erc-join)
+                   (require 'erc-services))
+
+(declare-function erc-network "erc-networks")
+(defvar erc-network)
+
+(defvar erc-scenarios-common--resources-dir
+  (expand-file-name "../" (ert-resource-directory)))
+
+;; Teardown is already inhibited when running interactively, which
+;; prevents subsequent tests from succeeding, so we might as well
+;; treat inspection as the goal.
+(unless noninteractive
+  (setq erc-server-auto-reconnect nil))
+
+(defvar erc-scenarios-common-dialog nil)
+(defvar erc-scenarios-common-extra-teardown nil)
+
+(defun erc-scenarios-common--add-silence ()
+  (advice-add #'erc-login :around #'erc-d-t-silence-around)
+  (advice-add #'erc-handle-login :around #'erc-d-t-silence-around)
+  (advice-add #'erc-server-connect :around #'erc-d-t-silence-around))
+
+(defun erc-scenarios-common--remove-silence ()
+  (advice-remove #'erc-login #'erc-d-t-silence-around)
+  (advice-remove #'erc-handle-login #'erc-d-t-silence-around)
+  (advice-remove #'erc-server-connect #'erc-d-t-silence-around))
+
+(defun erc-scenarios-common--print-trace ()
+  (when (and (boundp 'trace-buffer) (get-buffer trace-buffer))
+    (with-current-buffer trace-buffer
+      (message "%S" (buffer-string))
+      (kill-buffer))))
+
+(eval-and-compile
+  (defun erc-scenarios-common--make-bindings (bindings)
+    `((erc-d-u-canned-dialog-dir (expand-file-name
+                                  (or erc-scenarios-common-dialog
+                                      (cadr (assq 'erc-scenarios-common-dialog
+                                                  ',bindings)))
+                                  erc-scenarios-common--resources-dir))
+      (erc-d-tmpl-vars `(,@erc-d-tmpl-vars
+                         (quit . ,(erc-quit/part-reason-default))
+                         (erc-version . ,erc-version)))
+      (erc-modules (copy-sequence erc-modules))
+      (inhibit-interaction t)
+      (auth-source-do-cache nil)
+      (erc-auth-source-parameters-join-function nil)
+      (erc-autojoin-channels-alist nil)
+      (erc-server-auto-reconnect nil)
+      ,@bindings)))
+
+(defmacro erc-scenarios-common-with-cleanup (bindings &rest body)
+  "Provide boilerplate cleanup tasks after calling BODY with BINDINGS.
+
+If an `erc-d' process exists, wait for it to start before running BODY.
+If `erc-autojoin-mode' mode is bound, restore it during cleanup if
+disabled by BODY.  Other defaults common to these test cases are added
+below and can be overridden, except when wanting the \"real\" default
+value, which must be looked up or captured outside of the calling form.
+
+Dialog resource directories are located by expanding the variable
+`erc-scenarios-common-dialog' or its value in BINDINGS."
+  (declare (indent 1))
+
+  (let* ((orig-autojoin-mode (make-symbol "orig-autojoin-mode"))
+         (combind `((,orig-autojoin-mode (bound-and-true-p erc-autojoin-mode))
+                    ,@(erc-scenarios-common--make-bindings bindings))))
+
+    `(erc-d-t-with-cleanup (,@combind)
+
+         (ert-info ("Restore autojoin, etc., kill ERC buffers")
+           (dolist (buf (buffer-list))
+             (when-let ((erc-d-u--process-buffer)
+                        (proc (get-buffer-process buf)))
+               (erc-d-t-wait-for 5 "Dumb server dies on its own"
+                 (not (process-live-p proc)))))
+
+           (erc-scenarios-common--remove-silence)
+
+           (when erc-scenarios-common-extra-teardown
+             (ert-info ("Running extra teardown")
+               (funcall erc-scenarios-common-extra-teardown)))
+
+           (when (and (boundp 'erc-autojoin-mode)
+                      (not (eq erc-autojoin-mode ,orig-autojoin-mode)))
+             (erc-autojoin-mode (if ,orig-autojoin-mode +1 -1)))
+
+           (when noninteractive
+             (erc-scenarios-common--print-trace)
+             (erc-d-t-kill-related-buffers)
+             (delete-other-windows)))
+
+       (erc-scenarios-common--add-silence)
+
+       (ert-info ("Wait for dumb server")
+         (dolist (buf (buffer-list))
+           (with-current-buffer buf
+             (when erc-d-u--process-buffer
+               (erc-d-t-search-for 3 "Starting")))))
+
+       (ert-info ("Activate erc-debug-irc-protocol")
+         (unless (and noninteractive (not erc-debug-irc-protocol))
+           (erc-toggle-debug-irc-protocol)))
+
+       ,@body)))
+
+(defun erc-scenarios-common-assert-initial-buf-name (id port)
+  ;; Assert no limbo period when explicit ID given
+  (should (string= (if id
+                       (symbol-name id)
+                     (format "127.0.0.1:%d" port))
+                   (buffer-name))))
+
+(defun erc-scenarios-common-buflist (prefix)
+  "Return list of buffers with names sharing PREFIX."
+  (let (case-fold-search)
+    (erc-networks--id-sort-buffers
+     (delq nil
+           (mapcar (lambda (b)
+                     (when (string-prefix-p prefix (buffer-name b)) b))
+                   (buffer-list))))))
+
+;; This is more realistic than `erc-send-message' because it runs
+;; `erc-pre-send-functions', etc.  Keyboard macros may be preferable,
+;; but they sometimes experience complications when an earlier test
+;; has failed.
+(defun erc-scenarios-common-say (str)
+  (goto-char erc-input-marker)
+  (insert str)
+  (erc-send-current-line))
+
+
+;;;; Fixtures
+
+(cl-defun erc-scenarios-common--base-network-id-bouncer
+    ((&key autop foo-id bar-id after
+           &aux
+           (foo-id (and foo-id 'oofnet))
+           (bar-id (and bar-id 'rabnet))
+           (serv-buf-foo (if foo-id "oofnet" "foonet"))
+           (serv-buf-bar (if bar-id "rabnet" "barnet"))
+           (chan-buf-foo (if foo-id "#chan@oofnet" "#chan@foonet"))
+           (chan-buf-bar (if bar-id "#chan@rabnet" "#chan@barnet")))
+     &rest dialogs)
+  "Ensure retired option `erc-rename-buffers' is now the default behavior.
+The option `erc-rename-buffers' is now deprecated and on by default, so
+this now just asserts baseline behavior.  Originally from scenario
+clash-of-chans/rename-buffers as explained in Bug#48598: 28.0.50;
+buffer-naming collisions involving bouncers in ERC."
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/network-id/bouncer")
+       (erc-d-t-cleanup-sleep-secs 1)
+       (erc-server-flood-penalty 0.1)
+       (dumb-server (apply #'erc-d-run "localhost" t dialogs))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-auto-reconnect autop)
+       erc-server-buffer-foo erc-server-process-foo
+       erc-server-buffer-bar erc-server-process-bar)
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer
+          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "foonet:changeme"
+                                           :full-name "tester"
+                                           :id foo-id))
+        (setq erc-server-process-foo erc-server-process)
+        (erc-scenarios-common-assert-initial-buf-name foo-id port)
+        (erc-d-t-wait-for 3 (eq (erc-network) 'foonet))
+        (erc-d-t-wait-for 3 (string= (buffer-name) serv-buf-foo))
+        (funcall expect 5 "foonet")))
+
+    (ert-info ("Join #chan@foonet")
+      (with-current-buffer erc-server-buffer-foo (erc-cmd-JOIN "#chan"))
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
+        (funcall expect 5 "<alice>")))
+
+    (ert-info ("Connect to barnet")
+      (with-current-buffer
+          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "barnet:changeme"
+                                           :full-name "tester"
+                                           :id bar-id))
+        (setq erc-server-process-bar erc-server-process)
+        (erc-scenarios-common-assert-initial-buf-name bar-id port)
+        (erc-d-t-wait-for 6 (eq (erc-network) 'barnet))
+        (erc-d-t-wait-for 3 (string= (buffer-name) serv-buf-bar))
+        (funcall expect 5 "barnet")))
+
+    (ert-info ("Server buffers are unique, no names based on IPs")
+      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
+      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
+
+    (ert-info ("Join #chan@barnet")
+      (with-current-buffer erc-server-buffer-bar (erc-cmd-JOIN "#chan")))
+
+    (erc-d-t-wait-for 5 "Exactly 2 #chan-prefixed buffers exist"
+      (equal (list (get-buffer chan-buf-bar)
+                   (get-buffer chan-buf-foo))
+             (erc-scenarios-common-buflist "#chan")))
+
+    (ert-info ("#chan@<esid> is exclusive to foonet")
+      (with-current-buffer chan-buf-foo
+        (erc-d-t-search-for 1 "<bob>")
+        (erc-d-t-absent-for 0.1 "<joe>")
+        (should (eq erc-server-process erc-server-process-foo))
+        (while (accept-process-output erc-server-process-foo))
+        (erc-d-t-search-for 1 "ape is dead")
+        (should-not (erc-server-process-alive))))
+
+    (ert-info ("#chan@<esid> is exclusive to barnet")
+      (with-current-buffer chan-buf-bar
+        (erc-d-t-search-for 1 "<joe>")
+        (erc-d-t-absent-for 0.1 "<bob>")
+        (should (eq erc-server-process erc-server-process-bar))
+        (while (accept-process-output erc-server-process-bar))
+        (erc-d-t-search-for 1 "keeps you from dishonour")
+        (should-not (erc-server-process-alive))))
+
+    (when after (funcall after))))
+
+(defun erc-scenarios-common--clash-rename-pass-handler (dialog exchange)
+  (when (eq (erc-d-dialog-name dialog) 'stub-again)
+    (let* ((match (erc-d-exchange-match exchange 1))
+           (sym (if (string= match "foonet") 'foonet-again 'barnet-again)))
+      (should (member match (list "foonet" "barnet")))
+      (erc-d-load-replacement-dialog dialog sym 1))))
+
+(defun erc-scenarios-common--base-network-id-bouncer--reconnect (foo-id bar-id)
+  (let ((erc-d-tmpl-vars '((token . (group (| "barnet" "foonet")))))
+        (erc-d-match-handlers
+         ;; Auto reconnect is nondeterministic, so let computer decide
+         (list :pass #'erc-scenarios-common--clash-rename-pass-handler))
+        (after
+         (lambda ()
+           ;; Simulate disconnection and `erc-server-auto-reconnect'
+           (ert-info ("Reconnect to foonet and barnet back-to-back")
+             (with-current-buffer (if foo-id "oofnet" "foonet")
+               (erc-d-t-wait-for 5 (erc-server-process-alive)))
+             (with-current-buffer (if bar-id "rabnet" "barnet")
+               (erc-d-t-wait-for 5 (erc-server-process-alive))))
+
+           (ert-info ("#chan@foonet is exclusive to foonet")
+             (with-current-buffer (if foo-id "#chan@oofnet" "#chan@foonet")
+               (erc-d-t-search-for 1 "<alice>")
+               (erc-d-t-absent-for 0.1 "<joe>")
+               (while (accept-process-output erc-server-process))
+               (erc-d-t-search-for 20 "please your lordship")))
+
+           (ert-info ("#chan@barnet is exclusive to barnet")
+             (with-current-buffer (if bar-id "#chan@rabnet" "#chan@barnet")
+               (erc-d-t-search-for 1 "<joe>")
+               (erc-d-t-absent-for 0.1 "<bob>")
+               (while (accept-process-output erc-server-process))
+               (erc-d-t-search-for 1 "much in private")))
+
+           ;; XXX this is important (reconnects overlapped, so we'd get
+           ;; chan@127.0.0.1:6667)
+           (should-not (erc-scenarios-common-buflist "127.0.0.1"))
+           ;; Reconnection order doesn't matter here because session objects
+           ;; are persisted, meaning original timestamps preserved.
+           (should (equal (list (get-buffer (if bar-id "#chan@rabnet"
+                                              "#chan@barnet"))
+                                (get-buffer (if foo-id "#chan@oofnet"
+                                              "#chan@foonet")))
+                          (erc-scenarios-common-buflist "#chan"))))))
+    (erc-scenarios-common--base-network-id-bouncer
+     (list :autop t :foo-id foo-id :bar-id bar-id :after after)
+     'foonet-drop 'barnet-drop
+     'stub-again 'stub-again
+     'foonet-again 'barnet-again)))
+
+;; XXX this is okay, but we also need to check that target buffers are
+;; already associated with a new process *before* a JOIN is sent by a
+;; server's playback burst.  This doesn't do that.
+;;
+;; This *does* check that superfluous JOINs sent by the autojoin
+;; module are harmless when they're not acked (superfluous because the
+;; bouncer/server intitates the JOIN).
+
+(defun erc-scenarios-common--join-network-id (foo-reconnector foo-id bar-id)
+  "Ensure channels rejoined by erc-join.el DTRT.
+Originally from scenario clash-of-chans/autojoin as described in
+Bug#48598: 28.0.50; buffer-naming collisions involving bouncers in ERC."
+  (erc-scenarios-common-with-cleanup
+      ((chan-buf-foo (format "#chan@%s" (or foo-id "foonet")))
+       (chan-buf-bar (format "#chan@%s" (or bar-id "barnet")))
+       (erc-scenarios-common-dialog "join/network-id")
+       (erc-d-t-cleanup-sleep-secs 1)
+       (erc-server-flood-penalty 0.5)
+       (dumb-server (erc-d-run "localhost" t 'foonet 'barnet 'foonet-again))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       erc-server-buffer-foo erc-server-process-foo
+       erc-server-buffer-bar erc-server-process-bar)
+
+    (should (memq 'autojoin erc-modules))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer
+          (setq erc-server-buffer-foo (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "foonet:changeme"
+                                           :full-name "tester"
+                                           :id foo-id))
+        (setq erc-server-process-foo erc-server-process)
+        (erc-scenarios-common-assert-initial-buf-name foo-id port)
+        (erc-d-t-wait-for 1 (eq (erc-network) 'foonet))
+        (funcall expect 5 "foonet")))
+
+    (ert-info ("Join #chan, find sentinel, quit")
+      (with-current-buffer erc-server-buffer-foo (erc-cmd-JOIN "#chan"))
+      (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
+        (funcall expect 5 "vile thing")
+        (erc-cmd-QUIT "")))
+
+    (erc-d-t-wait-for 2 "Foonet connection deceased"
+      (not (erc-server-process-alive erc-server-buffer-foo)))
+
+    (should (equal erc-autojoin-channels-alist
+                   (if foo-id '((oofnet "#chan")) '((foonet "#chan")))))
+
+    (ert-info ("Connect to barnet")
+      (with-current-buffer
+          (setq erc-server-buffer-bar (erc :server "127.0.0.1"
+                                           :port port
+                                           :nick "tester"
+                                           :password "barnet:changeme"
+                                           :full-name "tester"
+                                           :id bar-id))
+        (setq erc-server-process-bar erc-server-process)
+        (erc-d-t-wait-for 5 (eq erc-network 'barnet))
+        (should (string= (buffer-name) (if bar-id "rabnet" "barnet")))))
+
+    (ert-info ("Server buffers are unique, no stray IP-based names")
+      (should-not (eq erc-server-buffer-foo erc-server-buffer-bar))
+      (should-not (erc-scenarios-common-buflist "127.0.0.1")))
+
+    (ert-info ("Only one #chan buffer exists")
+      (should (equal (list (get-buffer "#chan"))
+                     (erc-scenarios-common-buflist "#chan"))))
+
+    (ert-info ("#chan is not auto-joined")
+      (with-current-buffer "#chan"
+        (erc-d-t-absent-for 0.1 "<joe>")
+        (should-not (process-live-p erc-server-process))
+        (erc-d-t-ensure-for 0.1 "server buffer remains foonet"
+          (eq erc-server-process erc-server-process-foo))))
+
+    (with-current-buffer erc-server-buffer-bar
+      (erc-cmd-JOIN "#chan")
+      (erc-d-t-wait-for 3 (get-buffer chan-buf-foo))
+      (erc-d-t-wait-for 3 (get-buffer chan-buf-bar))
+      (with-current-buffer chan-buf-bar
+        (erc-d-t-wait-for 3 (eq erc-server-process erc-server-process-bar))
+        (funcall expect 5 "marry her instantly")))
+
+    (ert-info ("Reconnect to foonet")
+      (with-current-buffer (setq erc-server-buffer-foo
+                                 (funcall foo-reconnector))
+        (should (member (if foo-id '(oofnet "#chan") '(foonet "#chan"))
+                        erc-autojoin-channels-alist))
+        (erc-d-t-wait-for 3 (erc-server-process-alive))
+        (setq erc-server-process-foo erc-server-process)
+        (erc-d-t-wait-for 2 (eq erc-network 'foonet))
+        (should (string= (buffer-name) (if foo-id "oofnet" "foonet")))
+        (funcall expect 5 "foonet")))
+
+    (ert-info ("#chan@foonet is clean, no cross-contamination")
+      (with-current-buffer chan-buf-foo
+        (erc-d-t-wait-for 3 (eq erc-server-process erc-server-process-foo))
+        (funcall expect 3 "<bob>")
+        (erc-d-t-absent-for 0.1 "<joe>")
+        (while (accept-process-output erc-server-process-foo))
+        (funcall expect 3 "not given me")))
+
+    (ert-info ("All #chan@barnet output received")
+      (with-current-buffer chan-buf-bar
+        (while (accept-process-output erc-server-process-bar))
+        (funcall expect 3 "hath an uncle here")))))
+
+(provide 'erc-scenarios-common)
+
+;;; erc-scenarios-common.el ends here
diff --git a/test/lisp/erc/erc-scenarios-resources/join/legacy/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/join/legacy/foonet.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/join/legacy/foonet.eld
rename to test/lisp/erc/erc-scenarios/resources/join/legacy/foonet.eld
diff --git a/test/lisp/erc/erc-scenarios-resources/join/network-id/barnet.eld 
b/test/lisp/erc/erc-scenarios/resources/join/network-id/barnet.eld
similarity index 98%
rename from test/lisp/erc/erc-scenarios-resources/join/network-id/barnet.eld
rename to test/lisp/erc/erc-scenarios/resources/join/network-id/barnet.eld
index 1a13259383..e33dd6be29 100644
--- a/test/lisp/erc/erc-scenarios-resources/join/network-id/barnet.eld
+++ b/test/lisp/erc/erc-scenarios/resources/join/network-id/barnet.eld
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i"))
+((mode-user 12 "MODE tester +i"))
 ;; No mode answer
 
 ((join 2 "JOIN #chan")
diff --git 
a/test/lisp/erc/erc-scenarios-resources/join/network-id/foonet-again.eld 
b/test/lisp/erc/erc-scenarios/resources/join/network-id/foonet-again.eld
similarity index 97%
rename from 
test/lisp/erc/erc-scenarios-resources/join/network-id/foonet-again.eld
rename to test/lisp/erc/erc-scenarios/resources/join/network-id/foonet-again.eld
index 08e50dc62b..b230eff27c 100644
--- a/test/lisp/erc/erc-scenarios-resources/join/network-id/foonet-again.eld
+++ b/test/lisp/erc/erc-scenarios/resources/join/network-id/foonet-again.eld
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10.2 "MODE tester +i")
  ;; No mode answer ^
 
  ;; History
@@ -34,7 +34,7 @@
 ;; As a server, we ignore useless join sent by autojoin module
 ((~join 10 "JOIN #chan"))
 
-((mode-redux 1 "MODE #chan")
+((mode-redux 10 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620608304")
  (0.1 ":alice!~u@q6ddatxcq6txy.irc PRIVMSG #chan :bob: Ay, madam, with the 
swiftest wing of speed.")
diff --git a/test/lisp/erc/erc-scenarios-resources/join/network-id/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/join/network-id/foonet.eld
similarity index 95%
rename from test/lisp/erc/erc-scenarios-resources/join/network-id/foonet.eld
rename to test/lisp/erc/erc-scenarios/resources/join/network-id/foonet.eld
index 1162cc3f24..eb44e58a59 100644
--- a/test/lisp/erc/erc-scenarios-resources/join/network-id/foonet.eld
+++ b/test/lisp/erc/erc-scenarios/resources/join/network-id/foonet.eld
@@ -18,22 +18,22 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 1.2 "MODE tester +i"))
+((mode-user 10.2 "MODE tester +i"))
 ;; No mode answer ^
 
-((join 1 "JOIN #chan")
+((join 3 "JOIN #chan")
  (0 ":tester!~u@q6ddatxcq6txy.irc JOIN #chan")
  (0 ":irc.foonet.org 353 tester = #chan :@alice bob tester")
  (0 ":irc.foonet.org 366 tester #chan :End of NAMES list")
  (0.1 ":bob!~u@q6ddatxcq6txy.irc PRIVMSG #chan :tester, welcome!")
  (0 ":alice!~u@q6ddatxcq6txy.irc PRIVMSG #chan :tester, welcome!"))
 
-((mode 1 "MODE #chan")
+((mode 3 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620608304")
  (0.1 ":bob!~u@q6ddatxcq6txy.irc PRIVMSG #chan :alice: Pray you, sir, deliver 
me this paper.")
  (0.1 ":alice!~u@q6ddatxcq6txy.irc PRIVMSG #chan :bob: Wake when some vile 
thing is near."))
 
-((quit 1 "QUIT :\2ERC\2"))
+((quit 3 "QUIT :\2ERC\2"))
 
 ((drop 0 DROP))
diff --git 
a/test/lisp/erc/erc-scenarios-resources/join/reconnect/foonet-again.eld 
b/test/lisp/erc/erc-scenarios/resources/join/reconnect/foonet-again.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/join/reconnect/foonet-again.eld
rename to test/lisp/erc/erc-scenarios/resources/join/reconnect/foonet-again.eld
diff --git a/test/lisp/erc/erc-scenarios-resources/join/reconnect/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/join/reconnect/foonet.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/join/reconnect/foonet.eld
rename to test/lisp/erc/erc-scenarios/resources/join/reconnect/foonet.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/networks/announced-missing/foonet.eld 
b/test/lisp/erc/erc-scenarios/resources/networks/announced-missing/foonet.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/networks/announced-missing/foonet.eld
rename to 
test/lisp/erc/erc-scenarios/resources/networks/announced-missing/foonet.eld
diff --git 
a/test/lisp/erc/erc-scenarios-resources/services/auth-source/libera.eld 
b/test/lisp/erc/erc-scenarios/resources/services/auth-source/libera.eld
similarity index 100%
rename from 
test/lisp/erc/erc-scenarios-resources/services/auth-source/libera.eld
rename to test/lisp/erc/erc-scenarios/resources/services/auth-source/libera.eld
diff --git a/test/lisp/erc/erc-scenarios-resources/services/password/libera.eld 
b/test/lisp/erc/erc-scenarios/resources/services/password/libera.eld
similarity index 100%
rename from test/lisp/erc/erc-scenarios-resources/services/password/libera.eld
rename to test/lisp/erc/erc-scenarios/resources/services/password/libera.eld
diff --git a/test/lisp/erc/erc-services-tests.el 
b/test/lisp/erc/erc-services-tests.el
index f954d4a77e..947b343278 100644
--- a/test/lisp/erc/erc-services-tests.el
+++ b/test/lisp/erc/erc-services-tests.el
@@ -19,108 +19,181 @@
 
 ;;; Commentary:
 
-;; For convenience, some tests involving core auth-source
-;; functionality have been stashed here for the time being.
+;; TODO: move the auth-source tests somewhere else.  They've been
+;; stashed here for pragmatic reasons.
 
 ;;; Code:
 
 (require 'ert-x)
 (require 'erc-services)
 (require 'erc-compat)
+(require 'secrets)
 
 ;;;; Core auth-source
 
+(ert-deftest erc-auth-source-determine-params-merge ()
+  (let ((erc-session-server "irc.gnu.org")
+        (erc-server-announced-name "my.gnu.org")
+        (erc-session-port 6697)
+        (erc-network 'fake)
+        (erc-server-current-nick "tester")
+        (erc-networks--id (erc-networks--id-create 'GNU.chat)))
+
+    (should (equal (erc-auth-source-determine-params-merge)
+                   '(:host ("GNU.chat" "my.gnu.org" "irc.gnu.org")
+                           :port ("6697" "irc")
+                           :require (:secret))))
+
+    (should (equal (erc-auth-source-determine-params-merge :host "fake")
+                   '(:host ("fake" "GNU.chat" "my.gnu.org" "irc.gnu.org")
+                           :port ("6697" "irc")
+                           :require (:secret))))
+
+    (should (equal (erc-auth-source-determine-params-merge
+                    :host '("fake") :require :host)
+                   '(:host ("fake" "GNU.chat" "my.gnu.org" "irc.gnu.org")
+                           :require (:host :secret)
+                           :port ("6697" "irc"))))
+
+    (should (equal (erc-auth-source-determine-params-merge
+                    :host '("fake" "GNU.chat") :port "1234" :x "x")
+                   '(:host ("fake" "GNU.chat" "my.gnu.org" "irc.gnu.org")
+                           :port ("1234" "6697" "irc")
+                           :x ("x")
+                           :require (:secret))))))
+
 ;; Some of the following may be related to bug#23438.
 
-(defvar erc-join-tests--auth-source-entries
+(defun erc-services-tests--auth-source-standard (search)
+  (let ((filter #'erc-auth-source-determine-params-merge))
+
+    (ert-info ("Session wins")
+      (let ((erc-session-server "irc.gnu.org")
+            (erc-server-announced-name "my.gnu.org")
+            (erc-session-port 6697)
+            (erc-network 'fake)
+            (erc-server-current-nick "tester")
+            (erc-networks--id (erc-networks--id-create 'GNU.chat)))
+        (should-not (funcall search))
+        (should (string= (apply search (funcall filter :user "#chan"))
+                         "foo"))))
+
+    (ert-info ("Network wins")
+      (let* ((erc-session-server "irc.gnu.org")
+             (erc-server-announced-name "my.gnu.org")
+             (erc-session-port 6697)
+             (erc-network 'GNU.chat)
+             (erc-server-current-nick "tester")
+             (erc-networks--id (erc-networks--id-create nil)))
+        (should (string= (apply search (funcall filter :user "#chan"))
+                         "foo"))))
+
+    (ert-info ("Announced wins")
+      (let ((erc-session-server "irc.gnu.org")
+            (erc-server-announced-name "my.gnu.org")
+            (erc-session-port 6697)
+            erc-network
+            (erc-networks--id (erc-networks--id-create nil)))
+        (should (string= (apply search (funcall filter :user "#chan"))
+                         "baz"))))))
+
+(defun erc-services-tests--auth-source-announced (search)
+  (let* ((erc--isupport-params (make-hash-table))
+         (erc-server-parameters '(("CHANTYPES" . "&#")))
+         (erc--target (erc--target-from-string "&chan"))
+         (filter #'erc-auth-source-determine-params-merge))
+
+    (ert-info ("Announced prioritized")
+
+      (ert-info ("Announced wins")
+        (let* ((erc-session-server "irc.gnu.org")
+               (erc-server-announced-name "my.gnu.org")
+               (erc-session-port 6697)
+               (erc-network 'GNU.chat)
+               (erc-server-current-nick "tester")
+               (erc-networks--id (erc-networks--id-create nil)))
+          (should (string= (apply search (funcall filter :user "#chan"))
+                           "baz"))))
+
+      (ert-info ("Peer next")
+        (let* ((erc-server-announced-name "irc.gnu.org")
+               (erc-session-port 6697)
+               (erc-network 'GNU.chat)
+               (erc-server-current-nick "tester")
+               (erc-networks--id (erc-networks--id-create nil)))
+          (should (string= (apply search (funcall filter :user "#chan"))
+                           "bar"))))
+
+      (ert-info ("Network used as fallback")
+        (let* ((erc-session-port 6697)
+               (erc-network 'GNU.chat)
+               (erc-server-current-nick "tester")
+               (erc-networks--id (erc-networks--id-create nil)))
+          (should (string= (apply search (funcall filter :user "#chan"))
+                           "foo")))))))
+
+(defun erc-services-tests--auth-source-overrides (search)
+  (let* ((erc-session-server "irc.gnu.org")
+         (erc-server-announced-name "my.gnu.org")
+         (erc-network 'GNU.chat)
+         (erc-server-current-nick "tester")
+         (erc-networks--id (erc-networks--id-create nil))
+         (erc-session-port 6667)
+         (filter #'erc-auth-source-determine-params-merge))
+
+    (ert-info ("Specificity and overrides")
+
+      (ert-info ("More specific port")
+        (let ((erc-session-port 6697))
+          (should (string= (apply search (funcall filter :user "#chan"))
+                           "spam"))))
+
+      (ert-info ("More specific user (network loses)")
+        (should (string= (apply search (funcall filter :user '("#fsf")))
+                         "42")))
+
+      (ert-info ("Actual override")
+        (should (string= (apply search (funcall filter :port "6667"))
+                         "sesame")))
+
+      (ert-info ("Overrides don't interfere with post-processing")
+        (should (string= (apply search (funcall filter :host "MyHost"))
+                         "123"))))))
+
+;; auth-source netrc backend
+
+(defvar erc-services-tests--auth-source-entries
   '("machine irc.gnu.org port irc user \"#chan\" password bar"
     "machine my.gnu.org port irc user \"#chan\" password baz"
     "machine GNU.chat port irc user \"#chan\" password foo"))
 
 (defun erc-services-tests--auth-source-shuffle (&rest extra)
-  (string-join `(,@(sort (append erc-join-tests--auth-source-entries extra)
+  (string-join `(,@(sort (append erc-services-tests--auth-source-entries extra)
                          (lambda (&rest _) (zerop (random 2))))
                  "")
                "\n"))
 
-(ert-deftest erc--auth-source-search--standard ()
+(ert-deftest erc--auth-source-search--netrc-standard ()
   (ert-with-temp-file netrc-file
     :prefix "erc--auth-source-search--standard"
     :text (erc-services-tests--auth-source-shuffle)
+
     (let ((auth-sources (list netrc-file))
           (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-standard
+       #'erc--auth-source-search))))
 
-      (ert-info ("Normal ordering")
-
-        (ert-info ("Session wins")
-          (let ((erc-session-server "irc.gnu.org")
-                (erc-server-announced-name "my.gnu.org")
-                (erc-session-port 6697)
-                (erc-network 'fake)
-                (erc-server-current-nick "tester")
-                (erc-networks--id (erc-networks--id-create 'GNU.chat)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "foo"))))
-
-        (ert-info ("Network wins")
-          (let* ((erc-session-server "irc.gnu.org")
-                 (erc-server-announced-name "my.gnu.org")
-                 (erc-session-port 6697)
-                 (erc-network 'GNU.chat)
-                 (erc-server-current-nick "tester")
-                 (erc-networks--id (erc-networks--id-create nil)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "foo"))))
-
-        (ert-info ("Announced wins")
-          (let ((erc-session-server "irc.gnu.org")
-                (erc-server-announced-name "my.gnu.org")
-                (erc-session-port 6697)
-                erc-network
-                (erc-networks--id (erc-networks--id-create nil)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "baz"))))))))
-
-(ert-deftest erc--auth-source-search--announced ()
+(ert-deftest erc--auth-source-search--netrc-announced ()
   (ert-with-temp-file netrc-file
     :prefix "erc--auth-source-search--announced"
     :text (erc-services-tests--auth-source-shuffle)
-    (let* ((auth-sources (list netrc-file))
-           (auth-source-do-cache nil)
-           (erc--isupport-params (make-hash-table))
-           (erc-server-parameters '(("CHANTYPES" . "&#")))
-           (erc--target (erc--target-from-string "&chan")))
-
-      (ert-info ("Announced prioritized")
-
-        (ert-info ("Announced wins")
-          (let* ((erc-session-server "irc.gnu.org")
-                 (erc-server-announced-name "my.gnu.org")
-                 (erc-session-port 6697)
-                 (erc-network 'GNU.chat)
-                 (erc-server-current-nick "tester")
-                 (erc-networks--id (erc-networks--id-create nil)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "baz"))))
-
-        (ert-info ("Peer next")
-          (let* ((erc-server-announced-name "irc.gnu.org")
-                 (erc-session-port 6697)
-                 (erc-network 'GNU.chat)
-                 (erc-server-current-nick "tester")
-                 (erc-networks--id (erc-networks--id-create nil)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "bar"))))
-
-        (ert-info ("Network used as fallback")
-          (let* ((erc-session-port 6697)
-                 (erc-network 'GNU.chat)
-                 (erc-server-current-nick "tester")
-                 (erc-networks--id (erc-networks--id-create nil)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "foo"))))))))
-
-(ert-deftest erc--auth-source-search--overrides ()
+
+    (let ((auth-sources (list netrc-file))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-announced
+       #'erc--auth-source-search))))
+
+(ert-deftest erc--auth-source-search--netrc-overrides ()
   (ert-with-temp-file netrc-file
     :prefix "erc--auth-source-search--overrides"
     :text (erc-services-tests--auth-source-shuffle
@@ -130,33 +203,273 @@
            "machine MyHost port irc password 456"
            "machine MyHost port 6667 password 123")
 
-    (let* ((auth-sources (list netrc-file))
-           (auth-source-do-cache nil)
-           (erc-session-server "irc.gnu.org")
-           (erc-server-announced-name "my.gnu.org")
-           (erc-network 'GNU.chat)
-           (erc-server-current-nick "tester")
-           (erc-networks--id (erc-networks--id-create nil))
-           (erc-session-port 6667))
-
-      (ert-info ("Specificity and overrides")
-
-        (ert-info ("More specific port")
-          (let ((erc-session-port 6697))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "spam"))))
-
-        (ert-info ("More specific user (network loses)")
-          (should (string= (erc--auth-source-search :user '("#fsf"))
-                           "42")))
+    (let ((auth-sources (list netrc-file))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-overrides
+       #'erc--auth-source-search))))
+
+;; auth-source plstore backend
+
+(defun erc-services-test--call-with-plstore (&rest args)
+  (advice-add 'epg-decrypt-string :override
+              (lambda (&rest r) (prin1-to-string (cadr r)))
+              '((name . erc--auth-source-plstore)))
+  (advice-add 'epg-find-configuration :override
+              (lambda (&rest _) "" '((program . "/bin/true")))
+              '((name . erc--auth-source-plstore)))
+  (unwind-protect
+      (apply #'erc--auth-source-search args)
+    (advice-remove 'epg-decrypt-string 'erc--auth-source-plstore)
+    (advice-remove 'epg-find-configuration 'erc--auth-source-plstore)))
+
+(defvar erc-services-tests--auth-source-plstore-standard-entries
+  '(("ba950d38118a76d71f9f0591bb373d6cb366a512"
+     :secret-secret t
+     :host "irc.gnu.org"
+     :user "#chan"
+     :port "irc")
+    ("7f17ca445d11158065e911a6d0f4cbf52ca250e3"
+     :secret-secret t
+     :host "my.gnu.org"
+     :user "#chan"
+     :port "irc")
+    ("fcd3c8bd6daf4509de0ad6ee98e744ce0fca9377"
+     :secret-secret t
+     :host "GNU.chat"
+     :user "#chan"
+     :port "irc")))
+
+(defvar erc-services-tests--auth-source-plstore-standard-secrets
+  '(("ba950d38118a76d71f9f0591bb373d6cb366a512" :secret "bar")
+    ("7f17ca445d11158065e911a6d0f4cbf52ca250e3" :secret "baz")
+    ("fcd3c8bd6daf4509de0ad6ee98e744ce0fca9377" :secret "foo")))
+
+(ert-deftest erc--auth-source-search--plstore-standard ()
+  (ert-with-temp-file plstore-file
+    :suffix ".plist"
+    :text (concat ";;; public entries -*- mode: plstore -*- \n"
+                  (prin1-to-string
+                   erc-services-tests--auth-source-plstore-standard-entries)
+                  "\n;;; secret entries\n"
+                  (prin1-to-string
+                   erc-services-tests--auth-source-plstore-standard-secrets)
+                  "\n")
+
+    (let ((auth-sources (list plstore-file))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-standard
+       #'erc-services-test--call-with-plstore))))
+
+(ert-deftest erc--auth-source-search--plstore-announced ()
+  (ert-with-temp-file plstore-file
+    :suffix ".plist"
+    :text (concat ";;; public entries -*- mode: plstore -*- \n"
+                  (prin1-to-string
+                   erc-services-tests--auth-source-plstore-standard-entries)
+                  "\n;;; secret entries\n"
+                  (prin1-to-string
+                   erc-services-tests--auth-source-plstore-standard-secrets)
+                  "\n")
+
+    (let ((auth-sources (list plstore-file))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-announced
+       #'erc-services-test--call-with-plstore))))
+
+(ert-deftest erc--auth-source-search--plstore-overrides ()
+  (ert-with-temp-file plstore-file
+    :suffix ".plist"
+    :text (concat
+           ";;; public entries -*- mode: plstore -*- \n"
+           (prin1-to-string
+            `(,@erc-services-tests--auth-source-plstore-standard-entries
+              ("1b3fab249a8dff77a4d8fe7eb4b0171b25cc711a"
+               :secret-secret t :host "GNU.chat" :user "#chan" :port "6697")
+              ("6cbcdc39476b8cfcca6f3e9a7876f41ec3f708cc"
+               :secret-secret t :host "my.gnu.org" :user "#fsf" :port "irc")
+              ("a33e2b3bd2d6f33995a4b88710a594a100c5e41d"
+               :secret-secret t :host "irc.gnu.org" :port "6667")
+              ("ab2fd349b2b7d6a9215bb35a92d054261b0b1537"
+               :secret-secret t :host "MyHost" :port "irc")
+              ("61a6bd552059494f479ff720e8de33e22574650a"
+               :secret-secret t :host "MyHost" :port "6667")))
+           "\n;;; secret entries\n"
+           (prin1-to-string
+            `(,@erc-services-tests--auth-source-plstore-standard-secrets
+              ("1b3fab249a8dff77a4d8fe7eb4b0171b25cc711a" :secret "spam")
+              ("6cbcdc39476b8cfcca6f3e9a7876f41ec3f708cc" :secret "42")
+              ("a33e2b3bd2d6f33995a4b88710a594a100c5e41d" :secret "sesame")
+              ("ab2fd349b2b7d6a9215bb35a92d054261b0b1537" :secret "456")
+              ("61a6bd552059494f479ff720e8de33e22574650a" :secret "123")))
+           "\n")
+
+    (let ((auth-sources (list plstore-file))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-overrides
+       #'erc-services-test--call-with-plstore))))
+
+;; auth-source JSON backend
+
+(defvar erc-services-tests--auth-source-json-standard-entries
+  [(:host "irc.gnu.org" :port "irc" :user "#chan" :secret "bar")
+   (:host "my.gnu.org" :port "irc" :user "#chan" :secret "baz")
+   (:host "GNU.chat" :port "irc" :user "#chan" :secret "foo")])
+
+(ert-deftest erc--auth-source-search--json-standard ()
+  (ert-with-temp-file json-store
+    :suffix ".json"
+    :text (let ((json-object-type 'plist))
+            (json-encode
+             erc-services-tests--auth-source-json-standard-entries))
+    (let ((auth-sources (list json-store))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-standard
+       #'erc--auth-source-search))))
 
-        (ert-info ("Actual override")
-          (should (string= (erc--auth-source-search :port "6667")
-                           "sesame")))
+(ert-deftest erc--auth-source-search--json-announced ()
+  (ert-with-temp-file plstore-file
+    :suffix ".json"
+    :text (let ((json-object-type 'plist))
+            (json-encode
+             erc-services-tests--auth-source-json-standard-entries))
 
-        (ert-info ("Overrides don't interfere with post-processing")
-          (should (string= (erc--auth-source-search :host "MyHost")
-                           "123")))))))
+    (let ((auth-sources (list plstore-file))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-announced
+       #'erc--auth-source-search))))
+
+(ert-deftest erc--auth-source-search--json-overrides ()
+  (ert-with-temp-file json-file
+    :suffix ".json"
+    :text (let ((json-object-type 'plist))
+            (json-encode
+             (vconcat
+              erc-services-tests--auth-source-json-standard-entries
+              [(:secret "spam" :host "GNU.chat" :user "#chan" :port "6697")
+               (:secret "42" :host "my.gnu.org" :user "#fsf" :port "irc")
+               (:secret "sesame" :host "irc.gnu.org" :port "6667")
+               (:secret "456" :host "MyHost" :port "irc")
+               (:secret "123" :host "MyHost" :port "6667")])))
+
+    (let ((auth-sources (list json-file))
+          (auth-source-do-cache nil))
+      (erc-services-tests--auth-source-overrides
+       #'erc--auth-source-search))))
+
+;; auth-source-secrets backend
+
+(defvar erc-services-tests--auth-source-secrets-standard-entries
+  '(("#chan@irc.gnu.org:irc" ; label
+     (:host . "irc.gnu.org")
+     (:user . "#chan")
+     (:port . "irc")
+     (:xdg:schema . "org.freedesktop.Secret.Generic"))
+    ("#chan@my.gnu.org:irc"
+     (:host . "my.gnu.org")
+     (:user . "#chan")
+     (:port . "irc")
+     (:xdg:schema . "org.freedesktop.Secret.Generic"))
+    ("#chan@GNU.chat:irc"
+     (:host . "GNU.chat")
+     (:user . "#chan")
+     (:port . "irc")
+     (:xdg:schema . "org.freedesktop.Secret.Generic"))))
+
+(defvar erc-services-tests--auth-source-secrets-standard-secrets
+  '(("#chan@irc.gnu.org:irc" . "bar")
+    ("#chan@my.gnu.org:irc" . "baz")
+    ("#chan@GNU.chat:irc" . "foo")))
+
+(ert-deftest erc--auth-source-search--secrets-standard ()
+  (skip-unless (bound-and-true-p secrets-enabled))
+  (let ((auth-sources '("secrets:Test"))
+        (auth-source-do-cache nil)
+        (entries erc-services-tests--auth-source-secrets-standard-entries)
+        (secrets erc-services-tests--auth-source-secrets-standard-secrets))
+
+    (cl-letf (((symbol-function 'secrets-search-items)
+               (lambda (col &rest r)
+                 (should (equal col "Test"))
+                 (should (plist-get r :user))
+                 (map-keys entries)))
+              ((symbol-function 'secrets-get-secret)
+               (lambda (col label)
+                 (should (equal col "Test"))
+                 (assoc-default label secrets)))
+              ((symbol-function 'secrets-get-attributes)
+               (lambda (col label)
+                 (should (equal col "Test"))
+                 (assoc-default label entries))))
+
+      (erc-services-tests--auth-source-standard
+       #'erc--auth-source-search))))
+
+(ert-deftest erc--auth-source-search--secrets-announced ()
+  (skip-unless (bound-and-true-p secrets-enabled))
+  (let ((auth-sources '("secrets:Test"))
+        (auth-source-do-cache nil)
+        (entries erc-services-tests--auth-source-secrets-standard-entries)
+        (secrets erc-services-tests--auth-source-secrets-standard-secrets))
+
+    (cl-letf (((symbol-function 'secrets-search-items)
+               (lambda (col &rest r)
+                 (should (equal col "Test"))
+                 (should (plist-get r :user))
+                 (map-keys entries)))
+              ((symbol-function 'secrets-get-secret)
+               (lambda (col label)
+                 (should (equal col "Test"))
+                 (assoc-default label secrets)))
+              ((symbol-function 'secrets-get-attributes)
+               (lambda (col label)
+                 (should (equal col "Test"))
+                 (assoc-default label entries))))
+
+      (erc-services-tests--auth-source-announced
+       #'erc--auth-source-search))))
+
+(ert-deftest erc--auth-source-search--secrets-overrides ()
+  (skip-unless (bound-and-true-p secrets-enabled))
+  (let ((auth-sources '("secrets:Test"))
+        (auth-source-do-cache nil)
+        (entries `(,@erc-services-tests--auth-source-secrets-standard-entries
+                   ("#chan@GNU.chat:6697"
+                    (:host . "GNU.chat") (:user . "#chan") (:port . "6697")
+                    (:xdg:schema . "org.freedesktop.Secret.Generic"))
+                   ("#fsf@my.gnu.org:irc"
+                    (:host . "my.gnu.org") (:user . "#fsf") (:port . "irc")
+                    (:xdg:schema . "org.freedesktop.Secret.Generic"))
+                   ("irc.gnu.org:6667"
+                    (:host . "irc.gnu.org") (:port . "6667")
+                    (:xdg:schema . "org.freedesktop.Secret.Generic"))
+                   ("MyHost:irc"
+                    (:host . "MyHost") (:port . "irc")
+                    (:xdg:schema . "org.freedesktop.Secret.Generic"))
+                   ("MyHost:6667"
+                    (:host . "MyHost") (:port . "6667")
+                    (:xdg:schema . "org.freedesktop.Secret.Generic"))))
+        (secrets `(,@erc-services-tests--auth-source-secrets-standard-secrets
+                   ("#chan@GNU.chat:6697" . "spam")
+                   ("#fsf@my.gnu.org:irc" . "42" )
+                   ("irc.gnu.org:6667" . "sesame")
+                   ("MyHost:irc" . "456")
+                   ("MyHost:6667" . "123"))))
+
+    (cl-letf (((symbol-function 'secrets-search-items)
+               (lambda (col &rest _)
+                 (should (equal col "Test"))
+                 (map-keys entries)))
+              ((symbol-function 'secrets-get-secret)
+               (lambda (col label)
+                 (should (equal col "Test"))
+                 (assoc-default label secrets)))
+              ((symbol-function 'secrets-get-attributes)
+               (lambda (col label)
+                 (should (equal col "Test"))
+                 (assoc-default label entries))))
+
+      (erc-services-tests--auth-source-overrides
+       #'erc--auth-source-search))))
 
 ;; auth-source-pass backend
 
@@ -185,7 +498,8 @@
     ("GNU.chat:irc/#chan"
      ("port" . "irc") ("user" . "#chan") (secret . "foo"))))
 
-(ert-deftest erc-services-tests--auth-source-pass--standard ()
+(ert-deftest erc--auth-source-search--pass-standard ()
+  (ert-skip "Pass backend not yet supported")
   (let ((store erc-join-tests--auth-source-pass-entries)
         (auth-sources '(password-store))
         (auth-source-do-cache nil))
@@ -195,38 +509,10 @@
               ((symbol-function 'auth-source-pass-entries)
                (lambda () (mapcar #'car store))))
 
-      (ert-info ("Normal ordering")
-
-        (ert-info ("Session wins")
-          (let ((erc-session-server "irc.gnu.org")
-                (erc-server-announced-name "my.gnu.org")
-                (erc-session-port 6697)
-                (erc-network 'fake)
-                (erc-server-current-nick "tester")
-                (erc-networks--id (erc-networks--id-create 'GNU.chat)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "foo"))))
-
-        (ert-info ("Network wins")
-          (let* ((erc-session-server "irc.gnu.org")
-                 (erc-server-announced-name "my.gnu.org")
-                 (erc-session-port 6697)
-                 (erc-network 'GNU.chat)
-                 (erc-server-current-nick "tester")
-                 (erc-networks--id (erc-networks--id-create nil)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "foo"))))
-
-        (ert-info ("Announced wins")
-          (let ((erc-session-server "irc.gnu.org")
-                (erc-server-announced-name "my.gnu.org")
-                (erc-session-port 6697)
-                erc-network
-                (erc-networks--id (erc-networks--id-create nil)))
-            (should (string= (erc--auth-source-search :user "#chan")
-                             "baz"))))))))
-
-(ert-deftest erc-services-tests--auth-source-pass--announced ()
+      (erc-services-tests--auth-source-standard #'erc--auth-source-search))))
+
+(ert-deftest erc--auth-source-search--pass-announced ()
+  (ert-skip "Pass backend not yet supported")
   (let ((store erc-join-tests--auth-source-pass-entries)
         (auth-sources '(password-store))
         (auth-source-do-cache nil))
@@ -236,79 +522,31 @@
               ((symbol-function 'auth-source-pass-entries)
                (lambda () (mapcar #'car store))))
 
-      (let* ((erc--isupport-params (make-hash-table))
-             (erc-server-parameters '(("CHANTYPES" . "&#")))
-             (erc--target (erc--target-from-string "&chan")))
-
-        (ert-info ("Announced prioritized")
-
-          (ert-info ("Announced wins")
-            (let* ((erc-session-server "irc.gnu.org")
-                   (erc-server-announced-name "my.gnu.org")
-                   (erc-session-port 6697)
-                   (erc-network 'GNU.chat)
-                   (erc-server-current-nick "tester")
-                   (erc-networks--id (erc-networks--id-create nil)))
-              (should (string= (erc--auth-source-search :user "#chan")
-                               "baz"))))
-
-          (ert-info ("Peer next")
-            (let* ((erc-server-announced-name "irc.gnu.org")
-                   (erc-session-port 6697)
-                   (erc-network 'GNU.chat)
-                   (erc-server-current-nick "tester")
-                   (erc-networks--id (erc-networks--id-create nil)))
-              (should (string= (erc--auth-source-search :user "#chan")
-                               "bar"))))
-
-          (ert-info ("Network used as fallback")
-            (let* ((erc-session-port 6697)
-                   (erc-network 'GNU.chat)
-                   (erc-server-current-nick "tester")
-                   (erc-networks--id (erc-networks--id-create nil)))
-              (should (string= (erc--auth-source-search :user "#chan")
-                               "foo")))))))))
-
-(ert-deftest erc-services-tests--auth-source-pass--overrides ()
-  (let* ((store
-          `(,@erc-join-tests--auth-source-pass-entries
-            ("GNU.chat:6697/#chan"
-             ("port" . "6697") ("user" . "#chan") (secret . "spam"))
-            ("my.gnu.org:irc/#fsf"
-             ("port" . "irc") ("user" . "#fsf") (secret . "42"))
-            ("irc.gnu.org:6667"
-             ("port" . "6667") (secret . "sesame"))
-            ("MyHost:irc"
-             ("port" . "irc") (secret . "456"))
-            ("MyHost:6667"
-             ("port" . "6667") (secret . "123"))))
-         (auth-sources '(password-store))
-         (auth-source-do-cache nil)
-         (erc-session-server "irc.gnu.org")
-         (erc-server-announced-name "my.gnu.org")
-         (erc-network 'GNU.chat)
-         (erc-server-current-nick "tester")
-         (erc-networks--id (erc-networks--id-create nil))
-         (erc-session-port 6667))
+      (erc-services-tests--auth-source-announced #'erc--auth-source-search))))
+
+(ert-deftest erc--auth-source-search--pass-overrides ()
+  (ert-skip "Pass backend not yet supported")
+  (let ((store
+         `(,@erc-join-tests--auth-source-pass-entries
+           ("GNU.chat:6697/#chan"
+            ("port" . "6697") ("user" . "#chan") (secret . "spam"))
+           ("my.gnu.org:irc/#fsf"
+            ("port" . "irc") ("user" . "#fsf") (secret . "42"))
+           ("irc.gnu.org:6667"
+            ("port" . "6667") (secret . "sesame"))
+           ("MyHost:irc"
+            ("port" . "irc") (secret . "456"))
+           ("MyHost:6667"
+            ("port" . "6667") (secret . "123"))))
+        (auth-sources '(password-store))
+        (auth-source-do-cache nil))
 
     (cl-letf (((symbol-function 'auth-source-pass-parse-entry)
                (apply-partially #'erc-services-tests--asp-parse-entry store))
               ((symbol-function 'auth-source-pass-entries)
                (lambda () (mapcar #'car store))))
 
-      (ert-info ("More specific port")
-        (let ((erc-session-port 6697))
-          (should (string= (erc--auth-source-search :user "#chan") "spam"))))
-
-      (ert-info ("Network wins")
-        (should (string= (erc--auth-source-search :user '("#fsf")) "42")))
-
-      (ert-info ("Actual override")
-        (should (string= (erc--auth-source-search :port "6667") "sesame")))
-
-      (ert-info ("Overrides don't interfere with post-processing")
-        (should (string= (erc--auth-source-search :host "MyHost")
-                         "123"))))))
+      (erc-services-tests--auth-source-overrides #'erc--auth-source-search))))
 
 ;;;; The services module
 
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 0392005a43..be09207b0d 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -169,6 +169,14 @@
                                                    (get-buffer "ServNet"))
             erc-default-recipients '("#chan")))
 
+    (with-current-buffer (get-buffer-create "bob")
+      (erc-tests--send-prep)
+      (goto-char erc-insert-marker)
+      (should (looking-at-p (regexp-quote erc-prompt)))
+      (setq erc-server-process (buffer-local-value 'erc-server-process
+                                                   (get-buffer "ServNet"))
+            erc-default-recipients '("bob")))
+
     (ert-info ("Value: t (default)")
       (should (eq erc-hide-prompt t))
       (with-current-buffer "ServNet"
@@ -187,6 +195,17 @@
         (should-not (memq #'erc--unhide-prompt-on-self-insert
                           pre-command-hook)))
 
+      (with-current-buffer "bob"
+        (goto-char erc-insert-marker)
+        (should (string= ">" (get-text-property (point) 'display)))
+        (should (memq #'erc--unhide-prompt-on-self-insert pre-command-hook))
+        (goto-char erc-input-marker)
+        (ert-simulate-command '(self-insert-command 1 ?/))
+        (goto-char erc-insert-marker)
+        (should-not (get-text-property (point) 'display))
+        (should-not (memq #'erc--unhide-prompt-on-self-insert
+                          pre-command-hook)))
+
       (with-current-buffer "ServNet"
         (should (get-text-property erc-insert-marker 'display))
         (should (memq #'erc--unhide-prompt-on-self-insert pre-command-hook))
@@ -196,7 +215,7 @@
         (should-not (get-text-property erc-insert-marker 'display))))
 
     (ert-info ("Value: server")
-      (setq erc-hide-prompt 'server)
+      (setq erc-hide-prompt '(server))
       (with-current-buffer "ServNet"
         (erc--hide-prompt erc-server-process)
         (should (string= ">" (get-text-property erc-insert-marker 'display))))
@@ -204,27 +223,50 @@
       (with-current-buffer "#chan"
         (should-not (get-text-property erc-insert-marker 'display)))
 
+      (with-current-buffer "bob"
+        (should-not (get-text-property erc-insert-marker 'display)))
+
       (with-current-buffer "ServNet"
         (erc--unhide-prompt)
         (should-not (get-text-property erc-insert-marker 'display))))
 
-    (ert-info ("Value: target")
-      (setq erc-hide-prompt 'target)
+    (ert-info ("Value: channel")
+      (setq erc-hide-prompt '(channel))
       (with-current-buffer "ServNet"
         (erc--hide-prompt erc-server-process)
         (should-not (get-text-property erc-insert-marker 'display)))
 
+      (with-current-buffer "bob"
+        (should-not (get-text-property erc-insert-marker 'display)))
+
       (with-current-buffer "#chan"
         (should (string= ">" (get-text-property erc-insert-marker 'display)))
         (erc--unhide-prompt)
         (should-not (get-text-property erc-insert-marker 'display))))
 
+    (ert-info ("Value: query")
+      (setq erc-hide-prompt '(query))
+      (with-current-buffer "ServNet"
+        (erc--hide-prompt erc-server-process)
+        (should-not (get-text-property erc-insert-marker 'display)))
+
+      (with-current-buffer "bob"
+        (should (string= ">" (get-text-property erc-insert-marker 'display)))
+        (erc--unhide-prompt)
+        (should-not (get-text-property erc-insert-marker 'display)))
+
+      (with-current-buffer "#chan"
+        (should-not (get-text-property erc-insert-marker 'display))))
+
     (ert-info ("Value: nil")
       (setq erc-hide-prompt nil)
       (with-current-buffer "ServNet"
         (erc--hide-prompt erc-server-process)
         (should-not (get-text-property erc-insert-marker 'display)))
 
+      (with-current-buffer "bob"
+        (should-not (get-text-property erc-insert-marker 'display)))
+
       (with-current-buffer "#chan"
         (should-not (get-text-property erc-insert-marker 'display))
         (erc--unhide-prompt) ; won't blow up when prompt already showing
@@ -232,6 +274,7 @@
 
     (when noninteractive
       (kill-buffer "#chan")
+      (kill-buffer "bob")
       (kill-buffer "ServNet"))))
 
 (ert-deftest erc--switch-to-buffer ()
@@ -423,17 +466,17 @@
       (should (equal (erc-downcase "Tilde~") "tilde~" ))
       (should (equal (erc-downcase "\\O/") "|o/" )))))
 
-(ert-deftest erc-local-channel-p ()
+(ert-deftest erc--valid-local-channel-p ()
   (ert-info ("Local channels not supported")
     (let ((erc--isupport-params (make-hash-table)))
       (puthash 'CHANTYPES  '("#") erc--isupport-params)
-      (should-not (erc-valid-local-channel-p "#chan"))
-      (should-not (erc-valid-local-channel-p "&local"))))
+      (should-not (erc--valid-local-channel-p "#chan"))
+      (should-not (erc--valid-local-channel-p "&local"))))
   (ert-info ("Local channels supported")
     (let ((erc--isupport-params (make-hash-table)))
       (puthash 'CHANTYPES  '("&#") erc--isupport-params)
-      (should-not (erc-valid-local-channel-p "#chan"))
-      (should (erc-valid-local-channel-p "&local")))))
+      (should-not (erc--valid-local-channel-p "#chan"))
+      (should (erc--valid-local-channel-p "&local")))))
 
 (ert-deftest erc--target-from-string ()
   (should (equal (erc--target-from-string "#chan")



reply via email to

[Prev in Thread] Current Thread [Next in Thread]