aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Belaïche2026-03-29 06:17:55 +0200
committerVincent Belaïche2026-04-09 14:48:48 +0200
commit92a2d9abbd3d7884b6c6f442d5e57ae9ae06f9bd (patch)
treeb5d24aee4ededca2f98c4eb81cd24bbf07db9d6c
parent04dbd88c34341bc4fb1712f370eefd0d3387aa9d (diff)
downloademacs-scratch/ert-play-keys-pre-master.tar.gz
emacs-scratch/ert-play-keys-pre-master.zip
ert-play-keys functionscratch/ert-play-keys-pre-master
* lisp/emacs-lisp/ert-x.el (ert-play-keys): new defun. * test/lisp/simple-tests.el (undo-test-kill-c-a-then-undo) (undo-test-point-after-forward-kill): Use new function `ert-play-keys' and `(ert-with-test-buffer (:selected t) ...)' rather (with-temp-buffer (switch-to-buffer (current-buffer) ...)'. * test/lisp/erc/erc-scenarios-spelling.el (erc-scenarios-spelling--auto-correct): Use new function `ert-play-keys' and `(ert-with-buffer-selected ...)' rather than `execute-kbd-macro' and `(with-current-buffer ... (set-window-buffer nil (current-buffer) ...)'. * doc/misc/ert.texi (Helper Functions): Document ert-play-keys, and differences between ert-simulate-command, ert-simulate-keys & ert-play-keys. * test/lisp/emacs-lisp/ert-x-tests.el (ert-x-tests-play-keys) (ert-x-tests-simulate-command, ert-x-tests-simulate-keys): New tests.
-rw-r--r--doc/misc/ert.texi90
-rw-r--r--lisp/emacs-lisp/ert-x.el13
-rw-r--r--lisp/emacs-lisp/ert.el15
-rw-r--r--test/lisp/emacs-lisp/ert-x-tests.el71
-rw-r--r--test/lisp/erc/erc-scenarios-spelling.el7
-rw-r--r--test/lisp/simple-tests.el46
6 files changed, 205 insertions, 37 deletions
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
1132 (ert-with-test-buffer (:name "global" :selected t) 1132 (ert-with-test-buffer (:name "global" :selected t)
1133 @dots{})) 1133 @dots{}))
1134@end lisp 1134@end lisp
1135
1136@findex ert-play-keys
1137The @var{select-form} shall be set non-nil when @var{body} contains some
1138call to @code{ert-play-keys} to generate programmatically user input
1139events inserting text into the test buffer, or starting commands acting
1140on the test buffer.
1135@end defmac 1141@end defmac
1136 1142
1137@defmac ert-with-buffer-selected (buffer &body body) 1143@defmac ert-with-buffer-selected (buffer &body body)
@@ -1154,6 +1160,13 @@ value is the last form in @var{body}. Example:
1154@end lisp 1160@end lisp
1155 1161
1156This displays a temporary buffer like @file{ *temp*-739785*}. 1162This displays a temporary buffer like @file{ *temp*-739785*}.
1163
1164@findex ert-play-keys
1165One of the use of @code{ert-with-buffer-selected} is to set the buffer
1166to which user input events generated programmatically by one or more
1167calls to @code{ert-play-keys} are targetted, which is needed when those
1168events are supposed to insert text into this buffer, or start commands
1169acting on it.
1157@end defmac 1170@end defmac
1158 1171
1159@subsection Protecting buffers 1172@subsection Protecting buffers
@@ -1348,7 +1361,14 @@ symbol and the rest are arguments to the command. Example:
1348 1361
1349@strong{Note}: Since the command is not called by 1362@strong{Note}: Since the command is not called by
1350@code{call-interactively}, a test for @code{(called-interactively-p 1363@code{call-interactively}, a test for @code{(called-interactively-p
1351'interactive)} in the command will fail. 1364@var{kind})} in the command will fail for whatever @var{kind}.@*
1365Function @code{ert-play-keys} may be used instead to start a command if
1366you need the predicate @code{(called-interactively-p @var{kind})} tested
1367within the command body to return @code{t} for @var{kind} @code{any},
1368note however that it will still be @code{nil} too for @var{kind}
1369@code{interactive} since @code{ert-play-keys} uses keyboard macros under
1370the hood, and that @code{ert-play-keys} needs selecting the buffer of
1371interest.
1352@end defun 1372@end defun
1353 1373
1354@defmac ert-simulate-keys (keys &rest body) 1374@defmac ert-simulate-keys (keys &rest body)
@@ -1362,8 +1382,76 @@ vector. Examples:
1362(ert-simulate-keys (kbd "#fake C-m C-a C-k C-m") @dots{}) 1382(ert-simulate-keys (kbd "#fake C-m C-a C-k C-m") @dots{})
1363(ert-simulate-keys [?b ?2 return] @dots{}) 1383(ert-simulate-keys [?b ?2 return] @dots{})
1364@end lisp 1384@end lisp
1385
1386@c @findex ert-play-keys
1387To generate input event for inserting some text into a buffer, or
1388calling some interactive command, see rather function
1389@code{ert-play-keys}.
1365@end defmac 1390@end defmac
1366 1391
1392@defun ert-play-keys (keys)
1393Generate programmatically user input events.
1394
1395@c @findex ert-simulate-keys
1396Contrary to @code{ert-simulate-keys}, these events are not intended to be
1397consumed by functions reading input, like @code{read-from-minibuffer},
1398but are consumed by the command loop which typically will process them
1399to start interactive commands or insert text into the selected buffer.
1400
1401So, before calling @code{ert-play-keys} you generally need to select the
1402buffer to which input events are intended to insert text or call a
1403command. Do this by passing a non-nil @code{:selected} flag to
1404@code{ert-with-test-buffer} if the buffer was created this way, or use
1405the @code{ert-with-buffer-selected} macro.
1406
1407@c @findex ert-simulate-command
1408Contrary to @code{ert-simulate-command}, when @code{ert-play-keys}
1409generates events starting a command you cannot get the command return
1410value. On the other hand, @code{(called-interactively-p 'any)} tested in
1411the command body will be @code{t}, but not @code{(called-interactively-p
1412'interactive)} as @code{ert-play-keys} does not a true interactive call,
1413but uses a keyboard macro under the hood. Another difference is that,
1414contrary to @code{ert-simulate-command}, @code{ert-play-keys} needs to
1415select the buffer on which the command acts for the input events to
1416reach it.
1417
1418In this example a test buffer is created and selected, then
1419@code{ert-play-keys} sets the mark, inserts text @samp{n'importe quoi}
1420and kills it, then the test checks that the killed text is in the kill
1421ring and the test buffer is empty, then a second @code{ert-play-keys}
1422call yanks again the killed text, and finally the test checks the test
1423buffer contains @samp{n'importe quoi}:
1424
1425@lisp
1426(ert-deftest ert-example-kill&yank ()
1427 "Test kill and yank."
1428 (ert-with-test-buffer (:selected t)
1429 (ert-play-keys "C-SPC n'importe SPC quoi C-w")
1430 (should (string= "n'importe quoi" (car kill-ring)))
1431 (should (string= "" (buffer-substring (point-min) (point-max))))
1432 (ert-play-keys "C-y")
1433 (should (string= "n'importe quoi"
1434 (buffer-substring (point-min) (point-max))))))
1435@end lisp
1436
1437@noindent
1438Write input events as above with a string in the input format used by
1439@code{key-parse}, or directly in the internal Emacs representation like
1440here which is otherwise the same test as above:
1441
1442@lisp
1443(ert-deftest ert-example-kill&yank ()
1444 "Test kill and yank."
1445 (ert-with-test-buffer (:selected t)
1446 (ert-play-keys (vconcat [ ?\C- ] "n'importe quoi" [ ?\C-w]))
1447 (should (string= "n'importe quoi" (car kill-ring)))
1448 (should (string= "" (buffer-substring (point-min) (point-max))))
1449 (ert-play-keys [ ?\C-y ])
1450 (should (string= "n'importe quoi"
1451 (buffer-substring (point-min) (point-max))))))
1452@end lisp
1453@end defun
1454
1367@defun ert-filter-string (s &rest regexps) 1455@defun ert-filter-string (s &rest regexps)
1368This function returns a copy of string @var{s} with all matches of 1456This function returns a copy of string @var{s} with all matches of
1369@var{regexps} removed. Elements of @var{regexps} may also be 1457@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
416 (format "/mock::%s" temporary-file-directory)))) 416 (format "/mock::%s" temporary-file-directory))))
417 "Temporary directory for remote file tests.") 417 "Temporary directory for remote file tests.")
418 418
419(defun ert-play-keys (keys)
420 "Play the key sequence KEYS as if it was user input.
421
422KEYS shall have the same format as in a call to function `kmacro'.
423
424This macro should be expanded within the body of
425`ert-with-buffer-selected' to select a buffer when keys KEYS start
426commands acting on this buffer, or within the body of
427`ert-with-test-buffer' used with `:selected' flag set."
428 (funcall
429 (kmacro keys)))
430
431
419 432
420;;;; Obsolete 433;;;; Obsolete
421 434
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."
3125 3125
3126If BUFFER-OR-NAME is nil, the current buffer is used. 3126If BUFFER-OR-NAME is nil, the current buffer is used.
3127 3127
3128The buffer is made the current buffer, and the temporary window 3128The buffer is made the current buffer, and the temporary window becomes
3129becomes the `selected-window', before BODY is evaluated. The 3129the `selected-window', before BODY is evaluated. The modification hooks
3130modification hooks `before-change-functions' and 3130`before-change-functions' and `after-change-functions' are not inhibited
3131`after-change-functions' are not inhibited during the evaluation 3131during the evaluation of BODY, which makes it easier to use
3132of BODY, which makes it easier to use `execute-kbd-macro' to 3132`ert-play-keys' to simulate user sending input events. The window
3133simulate user interaction. The window configuration is restored 3133configuration is restored before returning, even if BODY exits
3134before returning, even if BODY exits nonlocally. The return 3134nonlocally. The return value is the last form in BODY."
3135value is the last form in BODY."
3136 (declare (debug (form body)) (indent 1)) 3135 (declare (debug (form body)) (indent 1))
3137 `(save-window-excursion 3136 `(save-window-excursion
3138 (with-current-buffer (or ,buffer-or-name (current-buffer)) 3137 (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."
293 (should-error 293 (should-error
294 (ert-with-temp-directory dir :text "foo" nil))) 294 (ert-with-temp-directory dir :text "foo" nil)))
295 295
296(ert-deftest ert-x-tests-play-keys ()
297 "Test `ert-play-keys'.
298Send one symbolic event, some inserted text, and some key event to the
299test buffer, and check all of them are processed."
300 (ert-with-test-buffer (:selected t)
301 (let (verdict-event verdict-key verdict-pre-command-hook verdict-post-command-hook)
302 (let ((pre-command-hook (lambda () (setq verdict-pre-command-hook t)))
303 (post-command-hook (lambda () (setq verdict-post-command-hook t)))
304 (map (let ((map (make-sparse-keymap)))
305 (define-key map [event]
306 (lambda ()
307 (interactive)
308 (setq verdict-event
309 (list t
310 (called-interactively-p 'any)
311 (called-interactively-p 'interactive)))))
312 (define-key map [?$]
313 (lambda ()
314 (interactive)
315 (setq verdict-key
316 (list t
317 (called-interactively-p 'any)
318 (called-interactively-p 'interactive)))))
319 map)))
320 (let ((minor-mode-map-alist (cons (cons t map) minor-mode-map-alist)))
321 (ert-play-keys (vconcat [event] "n'importe $quoi"))))
322 (should (equal verdict-event '(t t nil)))
323 (should (equal verdict-key '(t t nil)))
324 (should (eq verdict-pre-command-hook t))
325 (should (eq verdict-post-command-hook t)))
326 (should (string= "n'importe quoi"
327 (buffer-substring (point-min) (point-max))))))
328
329(ert-deftest ert-x-tests-simulate-command ()
330 "Test `ert-simulate-command'."
331 (ert-with-test-buffer ()
332 (let (verdict-interactive verdict-pre-command-hook verdict-post-command-hook)
333 (let ((pre-command-hook (lambda () (setq verdict-pre-command-hook t)))
334 (post-command-hook (lambda () (setq verdict-post-command-hook t))))
335 (should (eq (ert-simulate-command
336 (list
337 (lambda (x)
338 (interactive (list "un rien"))
339 (insert x)
340 (setq verdict-interactive (list t
341 (called-interactively-p 'any)
342 (called-interactively-p 'interactive)))
343 :ok)
344 "n'importe quoi"))
345 :ok)))
346 (should (equal verdict-interactive '(t nil nil)))
347 (should (eq verdict-pre-command-hook t))
348 (should (eq verdict-post-command-hook t)))
349 (should (string= "n'importe quoi"
350 (buffer-substring (point-min) (point-max))))))
351
352(ert-deftest ert-x-tests-simulate-keys ()
353 "Test `ert-simulate-keys'."
354 (ert-with-test-buffer ()
355 (let* ((map (let ((map (make-sparse-keymap)))
356 (define-key map [?b]
357 (lambda ()
358 (interactive)
359 (insert "r"))) map))
360 (minor-mode-map-alist (cons (cons t map) minor-mode-map-alist)))
361 (ert-simulate-keys
362 (listify-key-sequence "un bien\nn'importe quoi")
363 (should (string= (read-from-minibuffer "Please enter something: ") "un rien")))
364 (should (string= "" (buffer-substring (point-min) (point-max)))))))
365
366
296(provide 'ert-x-tests) 367(provide 'ert-x-tests)
297 368
298;;; ert-x-tests.el ends here 369;;; 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 @@
61 (should erc-spelling-mode) 61 (should erc-spelling-mode)
62 (should flyspell-mode))) 62 (should flyspell-mode)))
63 63
64 (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) 64 (ert-with-buffer-selected (erc-d-t-wait-for 10 (get-buffer "#chan"))
65 (should erc-spelling-mode) 65 (should erc-spelling-mode)
66 (should flyspell-mode) 66 (should flyspell-mode)
67 (funcall expect 10 "<alice> tester, welcome!") 67 (funcall expect 10 "<alice> tester, welcome!")
68 68
69 ;; Insert a command with one misspelled word. 69 ;; Insert a command with one misspelled word.
70 (set-window-buffer nil (current-buffer)) 70 (ert-play-keys (vconcat [?\M->] "/AMSG an/dor /gmsg one fsbot two frob my shoe"))
71 (execute-kbd-macro "\M->/AMSG an/dor /gmsg one fsbot two frob my shoe")
72 (funcall expect 10 "shoe") 71 (funcall expect 10 "shoe")
73 72
74 (let* ((ovs (overlays-in erc-input-marker (point))) 73 (let* ((ovs (overlays-in erc-input-marker (point)))
@@ -90,7 +89,7 @@
90 89
91 ;; Depending on the machine, this should become something 90 ;; Depending on the machine, this should become something
92 ;; like: "/AMSG an/dor /gmsg one fsbot two Rob my shoe". 91 ;; like: "/AMSG an/dor /gmsg one fsbot two Rob my shoe".
93 (execute-kbd-macro (key-parse "M-TAB")) 92 (ert-play-keys "M-TAB")
94 (should (equal (overlays-in erc-input-marker (point-max)) 93 (should (equal (overlays-in erc-input-marker (point-max))
95 (list ov1))))) 94 (list ov1)))))
96 95
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 @@
21 21
22;;; Code: 22;;; Code:
23 23
24(require 'ert) 24(require 'ert-x)
25(eval-when-compile (require 'cl-lib)) 25(eval-when-compile (require 'cl-lib))
26 26
27(defun simple-test--buffer-substrings () 27(defun simple-test--buffer-substrings ()
@@ -730,36 +730,34 @@ See bug#35036."
730;; Test for a regression introduced by undo-auto--boundaries changes. 730;; Test for a regression introduced by undo-auto--boundaries changes.
731;; https://lists.gnu.org/r/emacs-devel/2015-11/msg01652.html 731;; https://lists.gnu.org/r/emacs-devel/2015-11/msg01652.html
732(defun undo-test-kill-c-a-then-undo () 732(defun undo-test-kill-c-a-then-undo ()
733 (with-temp-buffer 733 (ert-with-test-buffer (:selected t)
734 (switch-to-buffer (current-buffer)) 734 (setq buffer-undo-list nil)
735 (setq buffer-undo-list nil) 735 (insert "a\nb\nc\n")
736 (insert "a\nb\nc\n") 736 (goto-char (point-max))
737 (goto-char (point-max)) 737 ;; We use a keyboard macro because it adds undo events in the same
738 ;; We use a keyboard macro because it adds undo events in the same 738 ;; way as if a user were involved.
739 ;; way as if a user were involved. 739 (ert-play-keys [left
740 (funcall (kmacro [left 740 ;; Delete "c"
741 ;; Delete "c" 741 backspace
742 backspace 742 left left left
743 left left left 743 ;; Delete "a"
744 ;; Delete "a" 744 backspace
745 backspace 745 ;; C-/ or undo
746 ;; C-/ or undo 746 ?\C-/
747 ?\C-/ 747 ])
748 ]))
749 (point))) 748 (point)))
750 749
751(defun undo-test-point-after-forward-kill () 750(defun undo-test-point-after-forward-kill ()
752 (with-temp-buffer 751 (ert-with-test-buffer (:selected t)
753 (switch-to-buffer (current-buffer))
754 (setq buffer-undo-list nil) 752 (setq buffer-undo-list nil)
755 (insert "kill word forward") 753 (insert "kill word forward")
756 ;; Move to word "word". 754 ;; Move to word "word".
757 (goto-char 6) 755 (goto-char 6)
758 (funcall (kmacro [;; kill-word 756 (ert-play-keys [;; kill-word
759 C-delete 757 C-delete
760 ;; undo 758 ;; undo
761 ?\C-/ 759 ?\C-/
762 ])) 760 ])
763 (point))) 761 (point)))
764 762
765(ert-deftest undo-point-in-wrong-place () 763(ert-deftest undo-point-in-wrong-place ()