diff --git a/doc/misc/ert.texi b/doc/misc/ert.texi index f3bd60db8e4..8a2128fe851 100644 --- a/doc/misc/ert.texi +++ b/doc/misc/ert.texi @@ -1132,6 +1132,12 @@ This has the same effect as combining @code{ert-with-test-buffer} with (ert-with-test-buffer (:name "global" :selected t) @dots{})) @end lisp + +@findex ert-play-keys +The @var{select-form} shall be set non-nil when @var{body} contains some +call to @code{ert-play-keys} to generate programmatically user input +events inserting text into the test buffer, or starting commands acting +on the test buffer. @end defmac @defmac ert-with-buffer-selected (buffer &body body) @@ -1154,6 +1160,13 @@ value is the last form in @var{body}. Example: @end lisp This displays a temporary buffer like @file{ *temp*-739785*}. + +@findex ert-play-keys +One of the use of @code{ert-with-buffer-selected} is to set the buffer +to which user input events generated programmatically by one or more +calls to @code{ert-play-keys} are targetted, which is needed when those +events are supposed to insert text into this buffer, or start commands +acting on it. @end defmac @subsection Protecting buffers @@ -1348,7 +1361,14 @@ symbol and the rest are arguments to the command. Example: @strong{Note}: Since the command is not called by @code{call-interactively}, a test for @code{(called-interactively-p -'interactive)} in the command will fail. +@var{kind})} in the command will fail for whatever @var{kind}.@* +Function @code{ert-play-keys} may be used instead to start a command if +you need the predicate @code{(called-interactively-p @var{kind})} tested +within the command body to return @code{t} for @var{kind} @code{any}, +note however that it will still be @code{nil} too for @var{kind} +@code{interactive} since @code{ert-play-keys} uses keyboard macros under +the hood, and that @code{ert-play-keys} needs selecting the buffer of +interest. @end defun @defmac ert-simulate-keys (keys &rest body) @@ -1362,8 +1382,76 @@ vector. Examples: (ert-simulate-keys (kbd "#fake C-m C-a C-k C-m") @dots{}) (ert-simulate-keys [?b ?2 return] @dots{}) @end lisp + +@c @findex ert-play-keys +To generate input event for inserting some text into a buffer, or +calling some interactive command, see rather function +@code{ert-play-keys}. @end defmac +@defun ert-play-keys (keys) +Generate programmatically user input events. + +@c @findex ert-simulate-keys +Contrary to @code{ert-simulate-keys}, these events are not intended to be +consumed by functions reading input, like @code{read-from-minibuffer}, +but are consumed by the command loop which typically will process them +to start interactive commands or insert text into the selected buffer. + +So, before calling @code{ert-play-keys} you generally need to select the +buffer to which input events are intended to insert text or call a +command. Do this by passing a non-nil @code{:selected} flag to +@code{ert-with-test-buffer} if the buffer was created this way, or use +the @code{ert-with-buffer-selected} macro. + +@c @findex ert-simulate-command +Contrary to @code{ert-simulate-command}, when @code{ert-play-keys} +generates events starting a command you cannot get the command return +value. On the other hand, @code{(called-interactively-p 'any)} tested in +the command body will be @code{t}, but not @code{(called-interactively-p +'interactive)} as @code{ert-play-keys} does not a true interactive call, +but uses a keyboard macro under the hood. Another difference is that, +contrary to @code{ert-simulate-command}, @code{ert-play-keys} needs to +select the buffer on which the command acts for the input events to +reach it. + +In this example a test buffer is created and selected, then +@code{ert-play-keys} sets the mark, inserts text @samp{n'importe quoi} +and kills it, then the test checks that the killed text is in the kill +ring and the test buffer is empty, then a second @code{ert-play-keys} +call yanks again the killed text, and finally the test checks the test +buffer contains @samp{n'importe quoi}: + +@lisp +(ert-deftest ert-example-kill&yank () + "Test kill and yank." + (ert-with-test-buffer (:selected t) + (ert-play-keys "C-SPC n'importe SPC quoi C-w") + (should (string= "n'importe quoi" (car kill-ring))) + (should (string= "" (buffer-substring (point-min) (point-max)))) + (ert-play-keys "C-y") + (should (string= "n'importe quoi" + (buffer-substring (point-min) (point-max)))))) +@end lisp + +@noindent +Write input events as above with a string in the input format used by +@code{key-parse}, or directly in the internal Emacs representation like +here which is otherwise the same test as above: + +@lisp +(ert-deftest ert-example-kill&yank () + "Test kill and yank." + (ert-with-test-buffer (:selected t) + (ert-play-keys (vconcat [ ?\C- ] "n'importe quoi" [ ?\C-w])) + (should (string= "n'importe quoi" (car kill-ring))) + (should (string= "" (buffer-substring (point-min) (point-max)))) + (ert-play-keys [ ?\C-y ]) + (should (string= "n'importe quoi" + (buffer-substring (point-min) (point-max)))))) +@end lisp +@end defun + @defun ert-filter-string (s &rest regexps) This function returns a copy of string @var{s} with all matches of @var{regexps} removed. Elements of @var{regexps} may also be diff --git a/lisp/emacs-lisp/ert-x.el b/lisp/emacs-lisp/ert-x.el index be4023c350a..0952033a6cc 100644 --- a/lisp/emacs-lisp/ert-x.el +++ b/lisp/emacs-lisp/ert-x.el @@ -416,6 +416,19 @@ The same keyword arguments are supported as in (format "/mock::%s" temporary-file-directory)))) "Temporary directory for remote file tests.") +(defun ert-play-keys (keys) + "Play the key sequence KEYS as if it was user input. + +KEYS shall have the same format as in a call to function `kmacro'. + +This macro should be expanded within the body of +`ert-with-buffer-selected' to select a buffer when keys KEYS start +commands acting on this buffer, or within the body of +`ert-with-test-buffer' used with `:selected' flag set." + (funcall + (kmacro keys))) + + ;;;; Obsolete diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el index 1c2df07f137..2f3f517701b 100644 --- a/lisp/emacs-lisp/ert.el +++ b/lisp/emacs-lisp/ert.el @@ -3125,14 +3125,13 @@ The return value is the last form in BODY." If BUFFER-OR-NAME is nil, the current buffer is used. -The buffer is made the current buffer, and the temporary window -becomes the `selected-window', before BODY is evaluated. The -modification hooks `before-change-functions' and -`after-change-functions' are not inhibited during the evaluation -of BODY, which makes it easier to use `execute-kbd-macro' to -simulate user interaction. The window configuration is restored -before returning, even if BODY exits nonlocally. The return -value is the last form in BODY." +The buffer is made the current buffer, and the temporary window becomes +the `selected-window', before BODY is evaluated. The modification hooks +`before-change-functions' and `after-change-functions' are not inhibited +during the evaluation of BODY, which makes it easier to use +`ert-play-keys' to simulate user sending input events. The window +configuration is restored before returning, even if BODY exits +nonlocally. The return value is the last form in BODY." (declare (debug (form body)) (indent 1)) `(save-window-excursion (with-current-buffer (or ,buffer-or-name (current-buffer)) diff --git a/test/lisp/emacs-lisp/ert-x-tests.el b/test/lisp/emacs-lisp/ert-x-tests.el index c72ab97c557..2823fdba3d7 100644 --- a/test/lisp/emacs-lisp/ert-x-tests.el +++ b/test/lisp/emacs-lisp/ert-x-tests.el @@ -293,6 +293,77 @@ desired effect." (should-error (ert-with-temp-directory dir :text "foo" nil))) +(ert-deftest ert-x-tests-play-keys () + "Test `ert-play-keys'. +Send one symbolic event, some inserted text, and some key event to the +test buffer, and check all of them are processed." + (ert-with-test-buffer (:selected t) + (let (verdict-event verdict-key verdict-pre-command-hook verdict-post-command-hook) + (let ((pre-command-hook (lambda () (setq verdict-pre-command-hook t))) + (post-command-hook (lambda () (setq verdict-post-command-hook t))) + (map (let ((map (make-sparse-keymap))) + (define-key map [event] + (lambda () + (interactive) + (setq verdict-event + (list t + (called-interactively-p 'any) + (called-interactively-p 'interactive))))) + (define-key map [?$] + (lambda () + (interactive) + (setq verdict-key + (list t + (called-interactively-p 'any) + (called-interactively-p 'interactive))))) + map))) + (let ((minor-mode-map-alist (cons (cons t map) minor-mode-map-alist))) + (ert-play-keys (vconcat [event] "n'importe $quoi")))) + (should (equal verdict-event '(t t nil))) + (should (equal verdict-key '(t t nil))) + (should (eq verdict-pre-command-hook t)) + (should (eq verdict-post-command-hook t))) + (should (string= "n'importe quoi" + (buffer-substring (point-min) (point-max)))))) + +(ert-deftest ert-x-tests-simulate-command () + "Test `ert-simulate-command'." + (ert-with-test-buffer () + (let (verdict-interactive verdict-pre-command-hook verdict-post-command-hook) + (let ((pre-command-hook (lambda () (setq verdict-pre-command-hook t))) + (post-command-hook (lambda () (setq verdict-post-command-hook t)))) + (should (eq (ert-simulate-command + (list + (lambda (x) + (interactive (list "un rien")) + (insert x) + (setq verdict-interactive (list t + (called-interactively-p 'any) + (called-interactively-p 'interactive))) + :ok) + "n'importe quoi")) + :ok))) + (should (equal verdict-interactive '(t nil nil))) + (should (eq verdict-pre-command-hook t)) + (should (eq verdict-post-command-hook t))) + (should (string= "n'importe quoi" + (buffer-substring (point-min) (point-max)))))) + +(ert-deftest ert-x-tests-simulate-keys () + "Test `ert-simulate-keys'." + (ert-with-test-buffer () + (let* ((map (let ((map (make-sparse-keymap))) + (define-key map [?b] + (lambda () + (interactive) + (insert "r"))) map)) + (minor-mode-map-alist (cons (cons t map) minor-mode-map-alist))) + (ert-simulate-keys + (listify-key-sequence "un bien\nn'importe quoi") + (should (string= (read-from-minibuffer "Please enter something: ") "un rien"))) + (should (string= "" (buffer-substring (point-min) (point-max))))))) + + (provide 'ert-x-tests) ;;; ert-x-tests.el ends here diff --git a/test/lisp/erc/erc-scenarios-spelling.el b/test/lisp/erc/erc-scenarios-spelling.el index 0495a2a1390..74e6dbb9715 100644 --- a/test/lisp/erc/erc-scenarios-spelling.el +++ b/test/lisp/erc/erc-scenarios-spelling.el @@ -61,14 +61,13 @@ (should erc-spelling-mode) (should flyspell-mode))) - (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (ert-with-buffer-selected (erc-d-t-wait-for 10 (get-buffer "#chan")) (should erc-spelling-mode) (should flyspell-mode) (funcall expect 10 " tester, welcome!") ;; Insert a command with one misspelled word. - (set-window-buffer nil (current-buffer)) - (execute-kbd-macro "\M->/AMSG an/dor /gmsg one fsbot two frob my shoe") + (ert-play-keys (vconcat [?\M->] "/AMSG an/dor /gmsg one fsbot two frob my shoe")) (funcall expect 10 "shoe") (let* ((ovs (overlays-in erc-input-marker (point))) @@ -90,7 +89,7 @@ ;; Depending on the machine, this should become something ;; like: "/AMSG an/dor /gmsg one fsbot two Rob my shoe". - (execute-kbd-macro (key-parse "M-TAB")) + (ert-play-keys "M-TAB") (should (equal (overlays-in erc-input-marker (point-max)) (list ov1))))) diff --git a/test/lisp/simple-tests.el b/test/lisp/simple-tests.el index 12697b2d68e..464cc51c152 100644 --- a/test/lisp/simple-tests.el +++ b/test/lisp/simple-tests.el @@ -21,7 +21,7 @@ ;;; Code: -(require 'ert) +(require 'ert-x) (eval-when-compile (require 'cl-lib)) (defun simple-test--buffer-substrings () @@ -730,36 +730,34 @@ See bug#35036." ;; Test for a regression introduced by undo-auto--boundaries changes. ;; https://lists.gnu.org/r/emacs-devel/2015-11/msg01652.html (defun undo-test-kill-c-a-then-undo () - (with-temp-buffer - (switch-to-buffer (current-buffer)) - (setq buffer-undo-list nil) - (insert "a\nb\nc\n") - (goto-char (point-max)) - ;; We use a keyboard macro because it adds undo events in the same - ;; way as if a user were involved. - (funcall (kmacro [left - ;; Delete "c" - backspace - left left left - ;; Delete "a" - backspace - ;; C-/ or undo - ?\C-/ - ])) + (ert-with-test-buffer (:selected t) + (setq buffer-undo-list nil) + (insert "a\nb\nc\n") + (goto-char (point-max)) + ;; We use a keyboard macro because it adds undo events in the same + ;; way as if a user were involved. + (ert-play-keys [left + ;; Delete "c" + backspace + left left left + ;; Delete "a" + backspace + ;; C-/ or undo + ?\C-/ + ]) (point))) (defun undo-test-point-after-forward-kill () - (with-temp-buffer - (switch-to-buffer (current-buffer)) + (ert-with-test-buffer (:selected t) (setq buffer-undo-list nil) (insert "kill word forward") ;; Move to word "word". (goto-char 6) - (funcall (kmacro [;; kill-word - C-delete - ;; undo - ?\C-/ - ])) + (ert-play-keys [;; kill-word + C-delete + ;; undo + ?\C-/ + ]) (point))) (ert-deftest undo-point-in-wrong-place ()