(require 'ert) (require 'edebug) (require 'helpful) (require 'python) (when noninteractive (unless find-function-C-source-directory (let* ((emacs-src-path (f-join default-directory "emacs-25.3" "src"))) (if (f-exists-p emacs-src-path) (setq find-function-C-source-directory emacs-src-path) (message "No Emacs source code found at %S, some tests will be skipped. Run ./download_emacs_src.sh" emacs-src-path)))) (setq jka-compr-verbose nil)) (defvar helpful-test-var nil) (define-obsolete-variable-alias 'helpful-test-var-obsolete 'helpful-test-var "23.1") (defun helpful-test-fun-obsolete () (declare (obsolete helpful "1.2.3")) nil) (defun test-foo () "Docstring here." nil) (defun test-foo-advised () "Docstring here too." nil) (autoload 'some-unused-function "somelib.el") (defadvice test-foo-advised (before test-advice1 activate) "Placeholder advice 1." nil) (defadvice test-foo-advised (after test-advice2 activate) "Placeholder advice 2." nil) (ert-deftest helpful--docstring () "Basic docstring fetching." (should (equal (helpful--docstring #'test-foo t) "Docstring here."))) (ert-deftest helpful--docstring-symbol () "Correctly handle quotes around symbols." ;; We should replace quoted symbols with links, so the punctuation ;; should not be in the output. (let* ((formatted-docstring (helpful--format-docstring "`message'"))) (should (equal formatted-docstring "message"))) ;; We should handle stray backquotes. (let* ((formatted-docstring (helpful--format-docstring "`foo `message'"))) (should (equal formatted-docstring "`foo message"))) ;; Handle a missing closing '. (let* ((formatted-docstring (helpful--format-docstring "`foo"))) (should (equal formatted-docstring "`foo")))) (ert-deftest helpful--docstring-unescape () "Discard \\=\\= in docstrings." (let* ((docstring (helpful--docstring #'apply t)) (formatted-docstring (helpful--format-docstring docstring))) (should (not (s-contains-p "\\=" formatted-docstring))))) (ert-deftest helpful--docstring-strings () "Double-quoted strings should be treated literally." ;; Ensure backslashes are shown escaped, so the output is a valid string literal. (let* ((formatted-docstring (helpful--format-docstring "hello \"x\\y\" world"))) ;; This test will fail in a local Emacs instance that has modified ;; minibuffer keybindings. (should (string-equal formatted-docstring "hello \"x\\\\y\" world"))) ;; Don't crash on unbalanced doublequotes. (helpful--format-docstring "hello \" world") ;; Command sequences \\[foo] should be ignored inside doublequotes. (let* ((formatted-docstring (helpful--format-docstring "hello \"\\[foo]\" world"))) ;; This test will fail in a local Emacs instance that has modified ;; minibuffer keybindings. (should (string-equal formatted-docstring "hello \"\\\\[foo]\" world")))) (ert-deftest helpful--docstring-keymap () "Handle keymap references in docstrings." (let* ((formatted-docstring (helpful--format-docstring "\\\\[next-history-element]"))) ;; This test will fail in a local Emacs instance that has modified ;; minibuffer keybindings. (should (string-equal formatted-docstring "M-n")))) (ert-deftest helpful--docstring-keymap-newline () "If a keymap reference is on its own line, remove the entire line." (should (string-equal (helpful--format-docstring "Foo. \\ bar") "Foo. bar"))) (ert-deftest helpful--docstring-advice () "Get the docstring on advised functions." (should (equal (helpful--docstring #'test-foo-advised t) "Docstring here too."))) (defun test-foo-no-docstring () nil) (ert-deftest helpful--no-docstring () "We should not crash on a function without a docstring." (should (null (helpful--docstring #'test-foo-no-docstring t)))) (ert-deftest helpful--interactively-defined-fn () "We should not crash on a function without source code." (eval '(defun test-foo-defined-interactively () 42)) (with-temp-buffer (helpful-function #'test-foo-defined-interactively) (should (equal (buffer-name) "*helpful function: test-foo-defined-interactively*")))) (ert-deftest helpful--edebug-fn () "We should not crash on a function with edebug enabled." (let ((edebug-all-forms t) (edebug-all-defs t)) (with-temp-buffer (insert "(defun test-foo-edebug () 44)") (goto-char (point-min)) (cl-letf (((symbol-function #'message) (lambda (_format-string &rest _args)))) (eval (eval-sexp-add-defvars (edebug-read-top-level-form)) t)))) (helpful-function #'test-foo-edebug)) (defun test-foo-return-arg (s) "blah blah." s) (ert-deftest helpful--edebug-p () "Ensure that we don't crash on a function whose body ends with symbol (not a form)." (should (not (helpful--edebug-p #'test-foo-return-arg)))) (defun test-foo-usage-docstring () "\n\n(fn &rest ARGS)" nil) (ert-deftest helpful--usage-docstring () "If a function docstring only has usage, do not return it." (should (null (helpful--docstring #'test-foo-usage-docstring t)))) (defun test-foo-no-properties () nil) (ert-deftest helpful--primitive-p () ;; Defined in C. (should (helpful--primitive-p 'message t)) ;; Defined in C, but an alias. (should (helpful--primitive-p 'not t)) ;; Defined in elisp. (should (not (helpful--primitive-p 'when t)))) (ert-deftest helpful--primitive-p--advised () "Ensure we handly advised primitive functions correctly." ;; `rename-buffer' is primitive, but it's advised by uniquify. (should (helpful--primitive-p 'rename-buffer t))) (ert-deftest helpful--without-advice () "Ensure we remove advice to get the underlying function." ;; Removing the advice on an unadvised function should give us the ;; same function. (should (eq (helpful--without-advice #'test-foo) (indirect-function #'test-foo))) ;; Removing the advice should give us an unadvised function. (should (not (helpful--advised-p (helpful--without-advice #'test-foo-advised))))) (ert-deftest helpful-callable () ;; Functions. Also a regression test for #170. (helpful-callable 'face-attribute) ;; We should not crash when looking at macros. (helpful-callable 'when) ;; Special forms should work too. (helpful-callable 'if) ;; Named keyboard macros (strings and vectors). (fset 'aaa "aaa") (helpful-callable 'aaa) (fset 'backspace-return [backspace return]) (helpful-callable 'backspace-return)) (ert-deftest helpful-callable--with-C-source () "Smoke test for special forms when we have the Emacs C source loaded." (skip-unless find-function-C-source-directory) (helpful-callable 'if)) (ert-deftest helpful--no-symbol-properties () "Helpful should handle functions without any symbol properties." ;; Interactively evaluating this file will set edebug properties on ;; test-foo-no-properties, so remove all properties. (setplist #'test-foo-no-properties nil) ;; This shouldn't throw any errors. (helpful-function #'test-foo-no-properties)) (ert-deftest helpful--split-first-line () ;; Don't modify a single line string. (should (equal (helpful--split-first-line "foo") "foo")) ;; Don't modify a two-line string if we don't end with . (should (equal (helpful--split-first-line "foo\nbar") "foo\nbar")) ;; If the second line is already empty, do nothing. (should (equal (helpful--split-first-line "foo.\n\nbar") "foo.\n\nbar")) ;; But if we have a single sentence and no empty line, insert one. (should (equal (helpful--split-first-line "foo.\nbar") "foo.\n\nbar"))) (ert-deftest helpful--format-reference () (should (equal (helpful--format-reference '(def foo) 10 1 123 "/foo/bar.el") "(def foo ...) 1 reference")) (should (equal (helpful--format-reference '(advice-add 'bar) 10 1 123 "/foo/bar.el") "(advice-add 'bar ...) 1 reference"))) (ert-deftest helpful--format-docstring () "Ensure we handle `foo' formatting correctly." ;; If it's bound, we should link it. (let* ((formatted (helpful--format-docstring "foo `message'.")) (m-position (s-index-of "m" formatted))) (should (get-text-property m-position 'button formatted))) ;; If it's not bound, we should not. (let* ((formatted (helpful--format-docstring "foo `messagexxx'.")) (m-position (s-index-of "m" formatted))) (should (not (get-text-property m-position 'button formatted))) ;; But we should always remove the backticks. (should (equal formatted "foo messagexxx."))) ;; Don't require the text between the quotes to be a valid symbol, e.g. ;; support `C-M-\' (found in `vhdl-mode'). (let* ((formatted (helpful--format-docstring "foo `C-M-\\'"))) (should (equal formatted "foo C-M-\\")))) (ert-deftest helpful--format-docstring-escapes () "Ensure we handle escaped quotes correctly." (let* ((formatted (helpful--format-docstring "foo \\=`message\\='.")) (m-position (s-index-of "m" formatted))) (should (equal formatted "foo `message'.")) (should (not (get-text-property m-position 'button formatted))))) (ert-deftest helpful--format-docstring-command-keys () "Ensure we propertize references to command key sequences." ;; This test will fail in your current Emacs instance if you've ;; overridden the `set-mark-command' keybinding. (-let [formatted (helpful--format-docstring "\\[set-mark-command]")] (should (string-equal formatted "C-SPC")) (should (get-text-property 0 'button formatted))) ;; If we have quotes around a key sequence, we should not propertize ;; it as the button styling will no longer be visible. (-let [formatted (helpful--format-docstring "`\\[set-mark-command]'")] (should (string-equal formatted "C-SPC")) (should (eq (get-text-property 0 'face formatted) 'button)))) (ert-deftest helpful--format-docstring-mode-maps () "Ensure we propertize references to keymaps." (-let [formatted (helpful--format-docstring "\\{python-mode-map}")] (should (s-contains-p "run-python" formatted))) ;; Handle non-existent mode maps gracefully. (-let [formatted (helpful--format-docstring "\\{no-such-mode-map}")] (should (s-contains-p "not currently defined" formatted)))) (ert-deftest helpful--format-docstring--info () "Ensure we propertize references to the info manual." ;; This is the typical format. (let* ((formatted (helpful--format-docstring "Info node `(elisp)foo'")) (paren-position (s-index-of "(" formatted))) (should (string-equal formatted "Info node (elisp)foo")) (should (get-text-property paren-position 'button formatted))) ;; Some functions, such as `signal', use 'anchor'. (let* ((formatted (helpful--format-docstring "Info anchor `(elisp)foo'")) (paren-position (s-index-of "(" formatted))) (should (string-equal formatted "Info anchor (elisp)foo")) (should (get-text-property paren-position 'button formatted))) ;; Ensure we handle wrapped lines too, e.g. in `org-odt-pixels-per-inch'. (let* ((formatted (helpful--format-docstring "Info node `(elisp)foo \nbar'")) (paren-position (s-index-of "(" formatted))) (should (string-equal formatted "Info node (elisp)foo \nbar")) (should (get-text-property paren-position 'button formatted))) ;; Some docstrings use "info" (lowercase). (let* ((formatted (helpful--format-docstring "info node `(elisp)foo'")) (paren-position (s-index-of "(" formatted))) (should (string-equal formatted "info node (elisp)foo")) (should (get-text-property paren-position 'button formatted))) ;; Some docstrings use angular quotation marks. (let* ((formatted (helpful--format-docstring "Info node ‘(elisp)foo’")) (paren-position (s-index-of "(" formatted))) (should (string-equal formatted "Info node (elisp)foo")) (should (get-text-property paren-position 'button formatted))) ;; If there's no manual information, assume it is part of the Emacs manual. (let* ((formatted (helpful--format-docstring "Info node ‘foo’")) (paren-position (s-index-of "(" formatted))) (should (string-equal formatted "Info node (emacs)foo")) (should (get-text-property paren-position 'button formatted)))) (ert-deftest helpful--format-docstring--url () "Ensure we propertize URLs with backticks." (let* ((formatted (helpful--format-docstring "URL `http://example.com'")) (url-position (s-index-of "h" formatted))) (should (string-equal formatted "URL http://example.com")) (should (get-text-property url-position 'button formatted)))) (ert-deftest helpful--format-docstring--bare-url () "Ensure we propertize URLs without backticks." (let* ((formatted (helpful--format-docstring "http://example.com\nbar")) (url-position (s-index-of "h" formatted))) (should (string-equal formatted "http://example.com\nbar")) (should (get-text-property url-position 'button formatted)) (should (equal (get-text-property url-position 'url formatted) "http://example.com"))) ;; Don't consider trailing punctuation to be part of the URL. (let* ((formatted (helpful--format-docstring "See http://example.com.")) (url-position (s-index-of "h" formatted))) (should (string-equal formatted "See http://example.com.")) (should (equal (get-text-property url-position 'url formatted) "http://example.com"))) ;; Format markdown-style links. (let* ((formatted (helpful--format-docstring "See .")) (url-position (s-index-of "h" formatted))) (should (equal (get-text-property url-position 'url formatted) "http://example.com")))) (ert-deftest helpful--definition-c-vars () "Handle definitions of variables in C source code." (skip-unless find-function-C-source-directory) (helpful--definition 'default-directory nil)) (ert-deftest helpful--definition-special-form () "Ensure we find the position of special forms." (skip-unless find-function-C-source-directory) (-let [(buf pos _) (helpful--definition 'if t)] (should buf) (should pos))) (setq helpful-var-without-defvar 'foo) (ert-deftest helpful--definition-no-defvar () "Ensure we don't crash on calling `helpful--definition' on variables defined without `defvar'." (helpful--definition 'helpful-var-without-defvar nil)) (ert-deftest helpful--definition-buffer-opened () "Ensure we mark buffers as opened for variables." (require 'python) ;; This test will fail if you already have python.el.gz open in your ;; Emacs instance. (skip-unless (null (get-buffer "python.el.gz"))) (-let [(buf pos opened) (helpful--definition 'python-indent-offset nil)] (should (bufferp buf)) (should opened))) (ert-deftest helpful--definition-edebug-fn () "Ensure we use the position information set by edebug, if present." ;; Test with both edebug enabled and disabled. The edebug property ;; on the symbol varies based on this. (dolist (edebug-on (list nil t)) (let ((edebug-all-forms edebug-on) (edebug-all-defs edebug-on)) (with-temp-buffer (insert "(defun test-foo-edebug-defn () 44)") (goto-char (point-min)) (cl-letf (((symbol-function #'message) (lambda (_format-string &rest _args)))) (eval (eval-sexp-add-defvars (edebug-read-top-level-form)) t)) (-let [(buf pos opened) (helpful--definition 'test-foo-edebug-defn t)] (should buf)))))) (ert-deftest helpful--definition-defstruct () "Ensure we find the position of struct functions." (-let [(buf pos _) (helpful--definition #'make-ert-test t)] (should buf) (should pos))) (ert-deftest helpful-variable () "Smoke test for `helpful-variable'." (helpful-variable 'tab-width)) (ert-deftest helpful-visit-reference () "Smoke test for `helpful-visit-reference'." (helpful-function 'replace-regexp-in-string) (goto-char (point-min)) ;; Move forward to the first reference. (while (not (get-text-property (point) 'helpful-pos)) (forward-char 1)) (helpful-visit-reference)) (ert-deftest helpful--signature () "Ensure that autoloaded functions are handled gracefully" (should (equal (helpful--signature 'some-unused-function) "(some-unused-function [Arg list not available until function definition is loaded.])"))) (ert-deftest helpful--signature-space () "Ensure that symbols with spaces are handled correctly." (should (equal (helpful--signature 'helpful-test-fn-with\ space) "(helpful-test-fn-with\\ space)"))) (ert-deftest helpful--signature--advertised () "Ensure that we respect functions that declare `advertised-calling-convention'." (should (equal (helpful--signature 'start-process-shell-command) "(start-process-shell-command NAME BUFFER COMMAND)"))) (ert-deftest helpful-function--single-buffer () "Ensure that calling `helpful-buffer' does not leave any extra buffers lying around." (let ((initial-buffers (buffer-list)) expected-buffers results-buffer) (helpful-function #'enable-theme) (setq results-buffer (get-buffer "*helpful command: enable-theme*")) (setq expected-buffers (cons results-buffer initial-buffers)) (should (null (-difference (buffer-list) expected-buffers))))) (ert-deftest helpful--kind-name () (should (equal (helpful--kind-name 'message nil) "variable")) (should (equal (helpful--kind-name 'message t) "function")) (should (equal (helpful--kind-name 'save-excursion t) "special form"))) (ert-deftest helpful--pretty-print () ;; Strings should be formatted with double-quotes. (should (equal "\"foo\"" (helpful--pretty-print "foo"))) ;; Don't crash on large plists using keywords. (helpful--pretty-print '(:foo foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo :bar bar))) (ert-deftest helpful-update-after-killing-buf () "If we originally looked at a variable in a specific buffer, and that buffer has been killed, handle it gracefully." ;; Don't crash if the underlying buffer has been killed. (let (helpful-buf) (with-temp-buffer (helpful-variable 'tab-width) (setq helpful-buf (current-buffer))) (with-current-buffer helpful-buf (helpful-update)))) (ert-deftest helpful--canonical-symbol () (should (eq (helpful--canonical-symbol 'not t) 'null)) (should (eq (helpful--canonical-symbol 'search-forward-regexp t) 're-search-forward)) (should (eq (helpful--canonical-symbol 'emacs-bzr-version nil) 'emacs-repository-version))) (ert-deftest helpful--aliases () (should (equal (helpful--aliases 'null t) (list 'not))) (should (equal (helpful--aliases 'emacs-repository-version nil) (list 'emacs-bzr-version)))) (defun helpful-fn-in-elc ()) (ert-deftest helpful--elc-only () "Ensure we handle functions where we have the .elc but no .el file." ;; Pretend that we've loaded `helpful-fn-in-elc' from /tmp/foo.elc. (let ((load-history (cons '("/tmp/foo.elc" (defun . helpful-fn-in-elc)) load-history))) ;; This should not error. (helpful-function 'helpful-fn-in-elc))) (ert-deftest helpful--unnamed-func () "Ensure we handle unnamed functions too. This is important for `helpful-key', where a user may have associated a lambda with a keybinding." (let* ((fun (lambda (x) x)) (buf (helpful--buffer fun t))) ;; There's no name, so just show lambda in the buffer name. (should (equal (buffer-name buf) "*helpful lambda*")) ;; Don't crash when we show the buffer. (with-current-buffer buf (helpful-update)))) (ert-deftest helpful--unnamed-compiled-func () "Ensure we handle unnamed byte-compiled functions. This is important for `helpful-key', where a user may have associated a lambda with a keybinding." (let* ((fun (byte-compile (lambda (x) x))) (buf (helpful--buffer fun t))) ;; There's no name, so just show lambda in the buffer name. (should (equal (buffer-name buf) "*helpful lambda*")) ;; Don't crash when we show the buffer. (with-current-buffer buf (helpful-update)))) (ert-deftest helpful--obsolete-variable () "Test display of obsolete variable." (let* ((var 'helpful-test-var-obsolete) (info (helpful--format-obsolete-info var nil))) (should (equal info "This variable is obsolete since 23.1; use helpful-test-var instead.")))) (ert-deftest helpful--obsolete-function () "Test display of obsolete function." (let* ((fun 'helpful-test-fun-obsolete) (info (helpful--format-obsolete-info fun t))) (should (equal info "This function is obsolete since 1.2.3; use helpful instead.")))) (ert-deftest helpful--keymap-keys--sparse () (let* ((parent-keymap (make-sparse-keymap)) (keymap (make-sparse-keymap))) (set-keymap-parent keymap parent-keymap) (define-key parent-keymap (kbd "a") #'forward-char) (define-key keymap (kbd "C-c C-M-a") #'backward-char) (define-key keymap [remap quoted-insert] #'forward-line) (should (equal (helpful--keymap-keys keymap) '(([17] forward-line) ([3 27 1] backward-char) ([97] forward-char)))))) (defvar helpful--dummy-keymap (let ((keymap (make-sparse-keymap))) (define-key keymap (kbd "a") #'forward-char) keymap)) ;; `fset' is necessary for keymaps as prefixes. This is a quirky Emacs ;; API: https://emacs.stackexchange.com/q/28576/304 (fset 'helpful--dummy-keymap helpful--dummy-keymap) (ert-deftest helpful--keymap-keys--prefix () "Test we flatten keymaps with prefix keys." (let* ((keymap (make-sparse-keymap))) (define-key keymap (kbd "C-c") 'helpful--dummy-keymap) (should (equal (helpful--keymap-keys keymap) '(([3 97] forward-char)))))) (ert-deftest helpful--keymap-keys () (let* ((parent-keymap (make-keymap)) (keymap (make-keymap))) (set-keymap-parent keymap parent-keymap) (define-key parent-keymap (kbd "a") #'forward-char) (define-key keymap (kbd "C-c C-M-a") #'backward-char) (define-key keymap [remap quoted-insert] #'forward-line) (should (equal ;; This order differs from a sparse keymap. We should fix that ;; if it makes any difference. (helpful--keymap-keys keymap) '(([3 27 1] backward-char) ([17] forward-line) ([97] forward-char)))))) (ert-deftest helpful--keymap-keys--strings () "Test that we handle maps with format (TYPE ITEM-NAME . BINDING)." ;; This is an actual piece of smerge-mode-map. (let ((keymap '(keymap (3 keymap (94 keymap (61 keymap (61 "upper-lower" . smerge-diff-upper-lower) (62 "base-lower" . smerge-diff-base-lower) (60 "base-upper" . smerge-diff-base-upper) "Diff")))))) (should (equal (helpful--keymap-keys keymap) '(([3 94 61 61] smerge-diff-upper-lower) ([3 94 61 62] smerge-diff-base-lower) ([3 94 61 60] smerge-diff-base-upper)))))) (ert-deftest helpful--keymap-keys--anonymous-fns () (let* ((keymap (make-keymap))) (define-key keymap (kbd "a") (lambda () (message))) (define-key keymap (kbd "a") (byte-compile-sexp (lambda () (message)))) ;; Don't crash on anonymous functions in a keymap. (helpful--keymap-keys keymap))) (ert-deftest helpful--format-keymap--keyboard-macros () (let* ((keymap (make-keymap))) ;; A keyboard macro can be a string or a vector. (define-key keymap "a" "ABC") (define-key keymap "b" [TAB]) (should (equal (helpful--format-keymap keymap) "a Keyboard Macro\nb Keyboard Macro")))) (defun helpful--dummy-command () (interactive)) (ert-deftest helpful--keymaps-containing () "Ensure that we find keymaps for variables with bindings." ;; This is defined in the global map. (should (helpful--keymaps-containing #'where-is)) ;; Only defined in `minor-mode-map-alist'. (let ((keymap (make-sparse-keymap))) (define-key keymap (kbd "a") #'helpful--dummy-command) (let ((minor-mode-map-alist (cons (cons 'foo-mode keymap) minor-mode-map-alist))) (should (helpful--keymaps-containing #'helpful--dummy-command)))) ;; Don't crash if there are dodgy values in `minor-mode-map-alist'. (let ((minor-mode-map-alist ;; I'm not convinced this is legal, but ;; pdf-cache-prefetch-minor-mode in pdf-tools has t as a ;; keymap. (cons (cons 'foo-mode t) minor-mode-map-alist))) (helpful--keymaps-containing #'helpful--dummy-command)) ;; Create a keybinding that is very unlikely to clobber actually ;; defined keybindings in the current emacs instance. (global-set-key (kbd "C-c M-S-c") #'helpful--dummy-command) ;; This command should only be in `global-map' and ;; `mode-specific-map'. (should (equal (length (helpful--keymaps-containing #'helpful--dummy-command)) 2)) ;; Undo keybinding. (global-set-key (kbd "C-c M-S-c") nil) ;; Check for ido command remapping. (ido-mode 1) (should (equal (helpful--keymaps-containing 'ido-find-file) '(("minor-mode-map-alist (ido-mode)" "" "C-x C-f")))) (ido-mode 0)) (defalias 'helpful--dummy-command-alias #'helpful--dummy-command) (ert-deftest helpful--keymaps-containing-aliases () "Ensure that we find keymaps that we've bound command aliases in." ;; Create keybindings that are very unlikely to clobber actually ;; defined keybindings in the current emacs instance. (global-set-key (kbd "C-c M-S-c") #'helpful--dummy-command) (global-set-key (kbd "C-c M-S-d") #'helpful--dummy-command-alias) (unwind-protect (let* ((keymaps (helpful--keymaps-containing-aliases #'helpful--dummy-command (helpful--aliases 'helpful--dummy-command t))) (global-keybindings (cdr (assoc "global-map" keymaps)))) (should (equal global-keybindings (list "C-c M-S-c" "C-c M-S-d")))) ;; Undo keybindings. (global-set-key (kbd "C-c M-S-c") nil) (global-set-key (kbd "C-c M-S-d") nil))) (ert-deftest helpful--merge-alists () (should (equal (helpful--merge-alists '((a . (1 2 3)) (b . (4))) '((a . (10)) (c . (11)))) '((a . (1 2 3 10)) (b . (4)) (c . (11)))))) (ert-deftest helpful--source () (-let* (((buf pos opened) (helpful--definition #'helpful--source t)) (source (helpful--source #'helpful--source t buf pos))) (should (s-starts-with-p "(defun " source)))) (ert-deftest helpful--source-autoloaded () "We should include the autoload cookie." (-let* (((buf pos opened) (helpful--definition #'helpful-at-point t)) (source (helpful--source #'helpful-at-point t buf pos))) (should (s-starts-with-p ";;;###autoload" source)))) (ert-deftest helpful--source--interactively-defined-fn () "We should return the raw sexp for functions where we can't find the source code." (eval '(defun test-foo-defined-interactively () 42)) (-let* (((buf pos opened) (helpful--definition #'test-foo-defined-interactively t))) (should (not (null (helpful--source #'test-foo-defined-interactively t buf pos)))))) (ert-deftest helpful--outer-sexp () ;; If point is in the middle of a form, we should return its position. (with-temp-buffer (insert "(foo bar baz)") (goto-char (point-min)) (search-forward "b") (-let [(pos subforms) (helpful--outer-sexp (current-buffer) (point))] (should (equal pos (point-min))) (should (equal subforms '(foo bar))))) ;; If point is at the beginning of a form, we should still return its position. (with-temp-buffer (insert "(foo) (bar)") (goto-char (point-min)) (search-forward "b") (backward-char 2) (-let [(pos subforms) (save-excursion (helpful--outer-sexp (current-buffer) (point)))] (should (equal pos (point))) (should (equal subforms '(bar)))))) (ert-deftest helpful--summary--aliases () ;; exclude the sym itself "Ensure we mention that a symbol is an alias." (-let* (((buf pos opened) (helpful--definition '-select t)) (summary (helpful--summary '-select t buf pos))) (when opened (kill-buffer buf)) ;; Strip properties to make assertion messages more readable. (set-text-properties 0 (1- (length summary)) nil summary) (should (equal summary "-select is a function alias for -filter, defined in dash.el.")))) (ert-deftest helpful--summary--special-form () "Ensure we describe special forms correctly" (-let* ((summary (helpful--summary 'if t nil nil))) ;; Strip properties to make assertion messages more readable. (set-text-properties 0 (1- (length summary)) nil summary) (should (s-starts-with-p "if is a special form defined in" summary)))) (defun helpful-test-fn-interactive () (interactive)) (ert-deftest helpful--summary--interactive-fn () "Ensure we use \"an\" for interactive functions." (let* ((summary (helpful--summary 'helpful-test-fn-interactive t nil nil))) ;; Strip properties to make assertion messages more readable. (set-text-properties 0 (1- (length summary)) nil summary) (should (s-starts-with-p "helpful-test-fn-interactive is an interactive function" summary)))) (defun helpful-test-fn? () (interactive)) (ert-deftest helpful--summary--fn-with-? () "Ensure we use don't needlessly escape ? in function names." (let* ((summary (helpful--summary 'helpful-test-fn? t nil nil))) ;; Strip properties to make assertion messages more readable. (set-text-properties 0 (1- (length summary)) nil summary) (should (s-starts-with-p "helpful-test-fn? is" summary)))) (ert-deftest helpful--signature-fn-with? () "Ensure that symbols with question marks are handled correctly." (should (equal (helpful--signature 'helpful-test-fn?) "(helpful-test-fn?)"))) (defun helpful-test-fn-with\ space () 42) (ert-deftest helpful--summary--symbol-with-space () "Ensure we correctly format symbols containing spaces." (let* ((summary (helpful--summary 'helpful-test-fn-with\ space t nil nil))) ;; Strip properties to make assertion messages more readable. (set-text-properties 0 (1- (length summary)) nil summary) (should (s-starts-with-p "helpful-test-fn-with\\ space is a function" summary)))) (ert-deftest helpful--bound-p () ;; Functions. (should (helpful--bound-p 'message)) ;; Variables (should (helpful--bound-p 'tab-width)) ;; Unbound. (should (not (helpful--bound-p 'this-variable-does-not-exist))) ;; For our purposes, we don't consider nil or t to be bound. (should (not (helpful--bound-p 'nil))) (should (not (helpful--bound-p 't)))) (ert-deftest helpful--callees () (should (equal (helpful--callees '(quote (foo))) nil)) ;; Simple function calls. (should (equal (helpful--callees '(foo (bar 1) 2)) '(foo bar)))) (ert-deftest helpful--callees-let () (should (equal (helpful--callees '(progn (let ((x (foo)) (y t)) (bar x y)) (let (y) (baz)) (let* ((z (quux)))))) '(foo bar baz quux)))) (ert-deftest helpful--callees--lambda () (should (equal (helpful--callees '(lambda (x) (foo x))) '(foo)))) (ert-deftest helpful--callees--closure () (should (equal (helpful--callees '(closure (t) (x) (foo x))) '(foo)))) (ert-deftest helpful--callees--function () (should (equal (helpful--callees '(function (lambda (x) (foo x)))) '(foo))) (should (equal (helpful--callees '(function foo)) '(foo)))) (ert-deftest helpful--callees--cond () (should (equal (helpful--callees '(cond (x) ((foo)) ((bar) (baz)) (t (quux)) )) '(foo bar baz quux)))) (ert-deftest helpful--callees--condition-case () (should (equal (helpful--callees '(condition-case e (foo) (error (bar)) ((arith-error file-error) (baz)))) '(foo bar baz)))) (ert-deftest helpful--callees--funcall () (let ((result (helpful--callees '(progn (funcall 'foo 1) (apply 'bar 2) (apply (baz) 3) (apply unknown-var 3))))) (should (memq 'foo result)) (should (memq 'bar result)) (should (memq 'baz result)) (should (not (memq 'unknown-var result)))) (let ((result (helpful--callees '(progn (funcall #'foo 1) (apply #'bar 2))))) (should (memq 'foo result)) (should (memq 'bar result)))) (ert-deftest helpful--callees-button--smoke () (with-temp-buffer (let ((button (helpful--make-callees-button 'whatever '(defun whatever () (something) (test 5))))) (insert button) (goto-char (point-min)) (push-button))) (with-temp-buffer (let ((button (helpful--make-callees-button '(lambda () (interactive) (other-window -1)) '(lambda () (interactive) (other-window -1))))) (insert button) (goto-char (point-min)) (push-button)))) (ert-deftest helpful--autoloaded-p () (-let [(buf pos opened) (helpful--definition 'rx-to-string t)] (should (helpful--autoloaded-p 'rx-to-string buf)) (when opened (kill-buffer buf)))) (ert-deftest helpful--inhibit-read-only () (helpful-variable 'inhibit-read-only) (should (s-contains-p "Value\nnil" (buffer-string)))) (ert-deftest helpful--convert-c-name () (should (equal 'make-string (helpful--convert-c-name 'Fmake_string nil))) (should (equal 'gc-cons-percentage (helpful--convert-c-name 'Vgc_cons_percentage t))) (should-not (helpful--convert-c-name 'Fmake_string t)) (should-not (helpful--convert-c-name 'Vgc_cons_percentage nil))) (ert-deftest helpful-symbol-c-style () (helpful-symbol 'Fget_char_property) (helpful-symbol 'Vinhibit_field_text_motion)) (ert-deftest helpful-symbol-unbound () "Ensure we inform the user if we're given an unbound symbol." (should (condition-case _ (helpful-symbol 'notboundtoanything) ('user-error t)))) (ert-deftest helpful--loads-autoload-symbol () "When asked to describe an autoloaded symbol, just load it." ;; This test assumes that you haven't loaded tetris.el.gz in your ;; current instance. (skip-unless (autoloadp (symbol-function 'tetris))) ;; This is a regression test: `tetris' has `tetris-mode-map' in its ;; docstring, so we can't display the mode map unless tetris.el.gz is ;; loaded. ;; (helpful-function #'tetris)) (defcustom helpful-test-custom-var 123 "I am an example custom variable." :type 'number :group 'helpful :package-version '(helpful . "1.2.3")) ;; Ensure the current value differs from the original value. (setq helpful-test-custom-var 456) (ert-deftest helpful--original-value () "Show the original value for defcustom variables." (helpful-variable 'helpful-test-custom-var) (should (s-contains-p "Original Value\n123" (buffer-string)))) (ert-deftest helpful--package-version () "Report when a variable was added" (helpful-variable 'helpful-test-custom-var) (should (s-contains-p (s-word-wrap 70 "This variable was added, or its default value changed, in helpful version 1.2.3.") (buffer-string)))) (ert-deftest helpful--display-implementations () (require 'xref) (helpful-function 'xref-location-marker) (should (s-contains-p "Implementations" (buffer-string))) (should (s-contains-p "((l xref-file-location))" (buffer-string))) (should (s-contains-p "((l xref-buffer-location))" (buffer-string))))