;;; em-cmpl-tests.el --- em-cmpl test suite -*- lexical-binding:t -*- ;; Copyright (C) 2023 Free Software Foundation, Inc. ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; Tests for Eshell's interactive completion. ;;; Code: (require 'ert) (require 'eshell) (require 'em-cmpl) (require 'em-dirs) (require 'em-hist) (require 'em-tramp) (require 'em-unix) (require 'eshell-tests-helpers (expand-file-name "eshell-tests-helpers" (file-name-directory (or load-file-name default-directory)))) (defvar eshell-test-value nil) (defun eshell-insert-and-complete (input) "Insert INPUT and invoke completion, returning the result." (insert input) (completion-at-point) (eshell-get-old-input)) (defun eshell-arguments-equal (actual expected) "Return t if ACTUAL and EXPECTED are equal, including properties of strings. ACTUAL and EXPECTED should both be lists of strings." (when (length= actual (length expected)) (catch 'not-equal (cl-mapc (lambda (i j) (unless (equal-including-properties i j) (throw 'not-equal nil))) actual expected) t))) (defun eshell-arguments-equal--equal-explainer (actual expected) "Explain the result of `eshell-arguments-equal'." `(nonequal-result (actual ,actual) (expected ,expected))) (put 'eshell-arguments-equal 'ert-explainer #'eshell-arguments-equal--equal-explainer) ;;; Tests: (ert-deftest em-cmpl-test/parse-arguments/pipeline () "Test that parsing arguments for completion discards earlier commands." (with-temp-eshell (let ((eshell-test-value '("foo" "bar"))) (insert "echo hi | cat") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) '("cat")))))) (ert-deftest em-cmpl-test/parse-arguments/multiple-dots () "Test parsing arguments with multiple dots like \".../\"." (with-temp-eshell (insert "echo .../file.txt") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(propertize "../../file.txt" 'pcomplete-arg-value ".../file.txt")))))) (ert-deftest em-cmpl-test/parse-arguments/variable/numeric () "Test parsing arguments with a numeric variable interpolation." (with-temp-eshell (let ((eshell-test-value 42)) (insert "echo $eshell-test-value") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(propertize "42" 'pcomplete-arg-value 42))))))) (ert-deftest em-cmpl-test/parse-arguments/variable/nil () "Test parsing arguments with a nil variable interpolation." (with-temp-eshell (let ((eshell-test-value nil)) (insert "echo $eshell-test-value") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(propertize "" 'pcomplete-arg-value nil))))))) (ert-deftest em-cmpl-test/parse-arguments/variable/list () "Test parsing arguments with a list variable interpolation." (with-temp-eshell (let ((eshell-test-value '("foo" "bar"))) (insert "echo $eshell-test-value") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(propertize "(\"foo\" \"bar\")" 'pcomplete-arg-value eshell-test-value))))))) (ert-deftest em-cmpl-test/parse-arguments/variable/splice () "Test parsing arguments with a spliced variable interpolation." (with-temp-eshell (let ((eshell-test-value '("foo" "bar"))) (insert "echo $@eshell-test-value") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) '("echo" "foo" "bar")))))) (ert-deftest em-cmpl-test/parse-arguments/unevaluated-subcommand () "Test that subcommands return a stub when parsing for completion." (with-temp-eshell (insert "echo {echo hi}") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(eshell-argument-stub 'subcommand))))) (with-temp-eshell (insert "echo ${echo hi}") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(propertize (eshell-argument-stub 'subcommand) 'escaped t)))))) (ert-deftest em-cmpl-test/parse-arguments/unevaluated-lisp-form () "Test that Lisp forms return a stub when parsing for completion." (with-temp-eshell (insert "echo (concat \"hi\")") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(eshell-argument-stub 'lisp))))) (with-temp-eshell (insert "echo $(concat \"hi\")") (should (eshell-arguments-equal (car (eshell-complete-parse-arguments)) `("echo" ,(propertize (eshell-argument-stub 'lisp) 'escaped t)))))) (ert-deftest em-cmpl-test/file-completion/unique () "Test completion of file names when there's a unique result." (with-temp-eshell (ert-with-temp-directory default-directory (write-region nil nil (expand-file-name "file.txt")) (should (equal (eshell-insert-and-complete "echo fi") "echo file.txt "))))) (ert-deftest em-cmpl-test/file-completion/non-unique () "Test completion of file names when there are multiple results." (with-temp-eshell (ert-with-temp-directory default-directory (write-region nil nil (expand-file-name "file.txt")) (write-region nil nil (expand-file-name "file.el")) (should (equal (eshell-insert-and-complete "echo fi") "echo file.")) ;; Now try completing again. (let ((minibuffer-message-timeout 0) (inhibit-message t)) (completion-at-point)) ;; FIXME: We can't use `current-message' here. (with-current-buffer (messages-buffer) (save-excursion (goto-char (point-max)) (forward-line -1) (should (looking-at "Complete, but not unique"))))))) (ert-deftest em-cmpl-test/file-completion/glob () "Test completion of file names using a glob." (with-temp-eshell (ert-with-temp-directory default-directory (write-region nil nil (expand-file-name "file.txt")) (write-region nil nil (expand-file-name "file.el")) (should (equal (eshell-insert-and-complete "echo fi*.el") "echo file.el "))))) (ert-deftest em-cmpl-test/file-completion/after-list () "Test completion of file names after previous list arguments. See bug#59956." (with-temp-eshell (ert-with-temp-directory default-directory (write-region nil nil (expand-file-name "file.txt")) (should (equal (eshell-insert-and-complete "echo (list 1 2) fi") "echo (list 1 2) file.txt "))))) (ert-deftest em-cmpl-test/command-completion () "Test completion of command names like \"command\"." (with-temp-eshell (should (equal (eshell-insert-and-complete "listif") "listify ")))) (ert-deftest em-cmpl-test/subcommand-completion () "Test completion of command names like \"{command}\"." (with-temp-eshell (should (equal (eshell-insert-and-complete "{ listif") "{ listify "))) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo ${ listif") "echo ${ listify ")))) (ert-deftest em-cmpl-test/lisp-symbol-completion () "Test completion of Lisp forms like \"#'symbol\" and \"`symbol\". See ." (with-temp-eshell (should (equal (eshell-insert-and-complete "echo #'system-nam") "echo #'system-name "))) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo `system-nam") "echo `system-name ")))) (ert-deftest em-cmpl-test/lisp-function-completion () "Test completion of Lisp forms like \"(func)\". See ." (with-temp-eshell (should (equal (eshell-insert-and-complete "echo (eshell/ech") "echo (eshell/echo"))) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo $(eshell/ech") "echo $(eshell/echo")))) (ert-deftest em-cmpl-test/special-ref-completion/type () "Test completion of the start of special references like \"#." (with-temp-eshell (should (equal (eshell-insert-and-complete "echo hi > # # # #\". See ." (let (bufname) (with-temp-buffer (setq bufname (rename-buffer "my-buffer" t)) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo hi > # #<%s> " bufname)))) (setq bufname (rename-buffer "another buffer" t)) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo hi > # #<%s> " (string-replace " " "\\ " bufname)))))))) (ert-deftest em-cmpl-test/special-ref-completion/buffer () "Test completion of special references like \"#\". See ." (let (bufname) (with-temp-buffer (setq bufname (rename-buffer "my-buffer" t)) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo hi > # # " bufname)))) (setq bufname (rename-buffer "another buffer" t)) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo hi > # # " (string-replace " " "\\ " bufname)))))))) (ert-deftest em-cmpl-test/variable-ref-completion () "Test completion of variable references like \"$var\". See ." (with-temp-eshell (should (equal (eshell-insert-and-complete "echo $system-nam") "echo $system-name ")))) (ert-deftest em-cmpl-test/quoted-variable-ref-completion () "Test completion of variable references like \"$'var'\". See ." (with-temp-eshell (should (equal (eshell-insert-and-complete "echo $'system-nam") "echo $'system-name' "))) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo $\"system-nam") "echo $\"system-name\" ")))) (ert-deftest em-cmpl-test/variable-ref-completion/directory () "Test completion of variable references that expand to directories. See ." (with-temp-eshell (should (equal (eshell-insert-and-complete "echo $PW") "echo $PWD/"))) (with-temp-eshell (let ((minibuffer-message-timeout 0) (inhibit-message t)) (should (equal (eshell-insert-and-complete "echo $PWD") "echo $PWD/")))) (with-temp-eshell (should (equal (eshell-insert-and-complete "echo $'PW") "echo $'PWD'/")))) (ert-deftest em-cmpl-test/variable-assign-completion () "Test completion of variable assignments like \"var=value\". See ." (with-temp-eshell (ert-with-temp-directory default-directory (write-region nil nil (expand-file-name "file.txt")) (should (equal (eshell-insert-and-complete "VAR=f") "VAR=file.txt "))))) (ert-deftest em-cmpl-test/variable-assign-completion/non-assignment () "Test completion of things that look like variable assignment, but aren't. For example, the second argument in \"tar --directory=dir\" looks like it could be a variable assignment, but it's not. We should let `pcomplete/tar' handle it instead. See ." (with-temp-eshell (ert-with-temp-directory default-directory (write-region nil nil (expand-file-name "file.txt")) (make-directory "dir") (should (equal (eshell-insert-and-complete "tar --directory=") "tar --directory=dir/"))))) (ert-deftest em-cmpl-test/user-ref-completion () "Test completion of user references like \"~user\". See ." (unwind-protect (with-temp-eshell (cl-letf (((symbol-function 'eshell-read-user-names) (lambda () (setq eshell-user-names '((1234 . "user")))))) (should (equal (eshell-insert-and-complete "echo ~us") "echo ~user/")))) ;; Clear the cached user names we set above. (setq eshell-user-names nil))) ;;; em-cmpl-tests.el ends here