helpful

a fork of Wilfred's helpful
git clone https://git.trogloxene.org/helpful.git
Log | Files | Refs | README

helpful-unit-test.el (39093B)


      1 ;;; helpful-unit-test.el -*- lexical-binding: t; -*-
      2 
      3 (require 'ert)
      4 (require 'edebug)
      5 (require 'helpful)
      6 (require 'python)
      7 
      8 (when noninteractive
      9   (unless find-function-C-source-directory
     10     (let* ((emacs-src-path (f-join default-directory "emacs-25.3" "src")))
     11       (if (f-exists-p emacs-src-path)
     12           (setq find-function-C-source-directory emacs-src-path)
     13         (message "No Emacs source code found at %S, some tests will be skipped. Run ./download_emacs_src.sh"
     14                  emacs-src-path))))
     15 
     16   (setq jka-compr-verbose nil))
     17 
     18 (defvar helpful-test-var nil)
     19 (define-obsolete-variable-alias 'helpful-test-var-obsolete
     20   'helpful-test-var "23.1")
     21 
     22 (defun helpful-test-fun-obsolete ()
     23   (declare (obsolete helpful "1.2.3"))
     24   nil)
     25 
     26 (defun test-foo ()
     27   "Docstring here."
     28   nil)
     29 
     30 (defun test-foo-advised ()
     31   "Docstring here too."
     32   nil)
     33 
     34 (autoload 'some-unused-function "somelib.el")
     35 
     36 (defadvice test-foo-advised (before test-advice1 activate)
     37   "Placeholder advice 1."
     38   nil)
     39 
     40 (defadvice test-foo-advised (after test-advice2 activate)
     41   "Placeholder advice 2."
     42   nil)
     43 
     44 (ert-deftest helpful--docstring ()
     45   "Basic docstring fetching."
     46   (should
     47    (equal
     48     (helpful--docstring #'test-foo t)
     49     "Docstring here.")))
     50 
     51 (ert-deftest helpful--docstring-symbol ()
     52   "Correctly handle quotes around symbols."
     53   ;; We should replace quoted symbols with links, so the punctuation
     54   ;; should not be in the output.
     55   (let* ((formatted-docstring (helpful--format-docstring "`message'")))
     56     (should
     57      (equal formatted-docstring "message")))
     58   ;; We should handle stray backquotes.
     59   (let* ((formatted-docstring (helpful--format-docstring "`foo `message'")))
     60     (should
     61      (equal formatted-docstring "`foo message")))
     62   ;; Handle a missing closing '.
     63   (let* ((formatted-docstring (helpful--format-docstring "`foo")))
     64     (should
     65      (equal formatted-docstring "`foo"))))
     66 
     67 (ert-deftest helpful--docstring-unescape ()
     68   "Discard \\=\\= in docstrings."
     69   (let* ((docstring (helpful--docstring #'apply t))
     70          (formatted-docstring (helpful--format-docstring docstring)))
     71     (should
     72      (not (s-contains-p "\\=" formatted-docstring)))))
     73 
     74 (ert-deftest helpful--docstring-strings ()
     75   "Double-quoted strings should be treated literally."
     76   ;; Ensure backslashes are shown escaped, so the output is a valid string literal.
     77   (let* ((formatted-docstring
     78           (helpful--format-docstring
     79            "hello \"x\\y\" world")))
     80     ;; This test will fail in a local Emacs instance that has modified
     81     ;; minibuffer keybindings.
     82     (should
     83      (string-equal formatted-docstring "hello \"x\\\\y\" world")))
     84   ;; Don't crash on unbalanced doublequotes.
     85   (helpful--format-docstring "hello \" world")
     86   ;; Command sequences \\[foo] should be ignored inside doublequotes.
     87   (let* ((formatted-docstring
     88           (helpful--format-docstring
     89            "hello \"\\[foo]\" world")))
     90     ;; This test will fail in a local Emacs instance that has modified
     91     ;; minibuffer keybindings.
     92     (should
     93      (string-equal formatted-docstring
     94                    "hello \"\\\\[foo]\" world"))))
     95 
     96 (ert-deftest helpful--docstring-keymap ()
     97   "Handle keymap references in docstrings."
     98   (let* ((formatted-docstring
     99           (helpful--format-docstring
    100            "\\<minibuffer-local-map>\\[next-history-element]")))
    101     ;; This test will fail in a local Emacs instance that has modified
    102     ;; minibuffer keybindings.
    103     (should
    104      (string-equal formatted-docstring "M-n"))))
    105 
    106 (ert-deftest helpful--docstring-keymap-newline ()
    107   "If a keymap reference is on its own line, remove the entire line."
    108   (should
    109    (string-equal
    110     (helpful--format-docstring "Foo.
    111 \\<minibuffer-local-map>
    112 bar")
    113     "Foo.
    114 
    115 bar")))
    116 
    117 (ert-deftest helpful--docstring-advice ()
    118   "Get the docstring on advised functions."
    119   (should
    120    (equal
    121     (helpful--docstring #'test-foo-advised t)
    122     (if (version< emacs-version "28")
    123         "Docstring here too."
    124     "Docstring here too.\n\nThis function has :around advice: `ad-Advice-test-foo-advised'."))))
    125 
    126 (defun test-foo-no-docstring ()
    127   nil)
    128 
    129 (ert-deftest helpful--no-docstring ()
    130   "We should not crash on a function without a docstring."
    131   (should (null (helpful--docstring #'test-foo-no-docstring t))))
    132 
    133 (ert-deftest helpful--interactively-defined-fn ()
    134   "We should not crash on a function without source code."
    135   (eval '(defun test-foo-defined-interactively () 42))
    136   (with-temp-buffer
    137     (helpful-function #'test-foo-defined-interactively)
    138     (should (equal (buffer-name) "*helpful function: test-foo-defined-interactively*"))))
    139 
    140 (ert-deftest helpful--variable-defined-at-point ()
    141   ;; The happy case with valid code.
    142   (with-temp-buffer
    143     (insert "(defvar foo nil)")
    144     (goto-char (1+ (point-min)))
    145     (should (eq (helpful--variable-defined-at-point) 'foo)))
    146   ;; Ensure we don't crash if the source code isn't valid.
    147   (with-temp-buffer
    148     (insert "(defvar foo nil")
    149     (goto-char (1+ (point-min)))
    150     (helpful--variable-defined-at-point)))
    151 
    152 (ert-deftest helpful--edebug-fn ()
    153   "We should not crash on a function with edebug enabled."
    154   (let ((edebug-all-forms t)
    155         (edebug-all-defs t))
    156     (with-temp-buffer
    157       (insert "(defun test-foo-edebug () 44)")
    158       (goto-char (point-min))
    159       (cl-letf (((symbol-function #'message)
    160                  (lambda (_format-string &rest _args))))
    161         (eval (eval-sexp-add-defvars (edebug-read-top-level-form)) t))))
    162   (helpful-function #'test-foo-edebug))
    163 
    164 (defun test-foo-return-arg (s)
    165   "blah blah."
    166   s)
    167 
    168 (ert-deftest helpful--edebug-p ()
    169   "Ensure that we don't crash on a function whose body ends with
    170 symbol (not a form)."
    171   (should
    172    (not (helpful--edebug-p #'test-foo-return-arg))))
    173 
    174 (defun test-foo-usage-docstring ()
    175   "\n\n(fn &rest ARGS)"
    176   nil)
    177 
    178 (ert-deftest helpful--usage-docstring ()
    179   "If a function docstring only has usage, do not return it."
    180   (should (null (helpful--docstring #'test-foo-usage-docstring t))))
    181 
    182 (defun test-foo-no-properties ()
    183   nil)
    184 
    185 (ert-deftest helpful--primitive-p ()
    186   ;; Defined in C.
    187   (should (helpful--primitive-p 'message t))
    188   ;; Defined in C, but an alias.
    189   (should (helpful--primitive-p 'not t))
    190   ;; Defined in elisp.
    191   (should (not (helpful--primitive-p 'when t))))
    192 
    193 (ert-deftest helpful--primitive-p--advised ()
    194   "Ensure we handly advised primitive functions correctly."
    195   ;; `rename-buffer' is primitive, but it's advised by uniquify.
    196   (should (helpful--primitive-p 'rename-buffer t)))
    197 
    198 (ert-deftest helpful--without-advice ()
    199   "Ensure we remove advice to get the underlying function."
    200   ;; Removing the advice on an unadvised function should give us the
    201   ;; same function.
    202   (should
    203    (eq (helpful--without-advice #'test-foo)
    204        (indirect-function #'test-foo)))
    205   ;; Removing the advice should give us an unadvised function.
    206   (should
    207    (not (helpful--advised-p
    208          (helpful--without-advice #'test-foo-advised)))))
    209 
    210 (ert-deftest helpful-callable ()
    211   ;; Functions. Also a regression test for #170.
    212   (helpful-callable 'face-attribute)
    213   ;; We should not crash when looking at macros.
    214   (helpful-callable 'when)
    215   ;; Special forms should work too.
    216   (helpful-callable 'if)
    217   ;; Named keyboard macros (strings and vectors).
    218   (fset 'aaa "aaa")
    219   (helpful-callable 'aaa)
    220   (fset 'backspace-return [backspace return])
    221   (helpful-callable 'backspace-return))
    222 
    223 (ert-deftest helpful-callable--with-C-source ()
    224   "Smoke test for special forms when we have the Emacs C source loaded."
    225   (skip-unless find-function-C-source-directory)
    226   (helpful-callable 'if))
    227 
    228 (ert-deftest helpful--no-symbol-properties ()
    229   "Helpful should handle functions without any symbol properties."
    230   ;; Interactively evaluating this file will set edebug properties on
    231   ;; test-foo-no-properties, so remove all properties.
    232   (setplist #'test-foo-no-properties nil)
    233 
    234   ;; This shouldn't throw any errors.
    235   (helpful-function #'test-foo-no-properties))
    236 
    237 (ert-deftest helpful--split-first-line ()
    238   ;; Don't modify a single line string.
    239   (should
    240    (equal (helpful--split-first-line "foo") "foo"))
    241   ;; Don't modify a two-line string if we don't end with .
    242   (should
    243    (equal (helpful--split-first-line "foo\nbar") "foo\nbar"))
    244   ;; If the second line is already empty, do nothing.
    245   (should
    246    (equal (helpful--split-first-line "foo.\n\nbar") "foo.\n\nbar"))
    247   ;; But if we have a single sentence and no empty line, insert one.
    248   (should
    249    (equal (helpful--split-first-line "foo.\nbar") "foo.\n\nbar")))
    250 
    251 (ert-deftest helpful--format-reference ()
    252   (should
    253    (equal
    254     (helpful--format-reference '(def foo) 10 1 123 "/foo/bar.el")
    255     "(def foo ...) 1 reference"))
    256   (should
    257    (equal
    258     (helpful--format-reference '(advice-add 'bar) 10 1 123 "/foo/bar.el")
    259     "(advice-add 'bar ...) 1 reference")))
    260 
    261 (ert-deftest helpful--format-docstring ()
    262   "Ensure we handle `foo' formatting correctly."
    263   ;; If it's bound, we should link it.
    264   (let* ((formatted (helpful--format-docstring "foo `message'."))
    265          (m-position (s-index-of "m" formatted)))
    266     (should (get-text-property m-position 'button formatted)))
    267   ;; If it's not bound, we should not.
    268   (let* ((formatted (helpful--format-docstring "foo `messagexxx'."))
    269          (m-position (s-index-of "m" formatted)))
    270     (should (not (get-text-property m-position 'button formatted)))
    271     ;; But we should always remove the backticks.
    272     (should (equal formatted "foo messagexxx.")))
    273   ;; Don't require the text between the quotes to be a valid symbol, e.g.
    274   ;; support `C-M-\' (found in `vhdl-mode').
    275   (let* ((formatted (helpful--format-docstring "foo `C-M-\\'")))
    276     (should (equal formatted "foo C-M-\\"))))
    277 
    278 (ert-deftest helpful--format-docstring-escapes ()
    279   "Ensure we handle escaped quotes correctly."
    280   (let* ((formatted (helpful--format-docstring "foo \\=`message\\='."))
    281          (m-position (s-index-of "m" formatted)))
    282     (should (equal formatted "foo `message'."))
    283     (should (not (get-text-property m-position 'button formatted)))))
    284 
    285 (ert-deftest helpful--format-docstring-command-keys ()
    286   "Ensure we propertize references to command key sequences."
    287   ;; This test will fail in your current Emacs instance if you've
    288   ;; overridden the `set-mark-command' keybinding.
    289   (-let [formatted (helpful--format-docstring "\\[set-mark-command]")]
    290     (should
    291      (string-equal formatted "C-SPC"))
    292     (should
    293      (get-text-property 0 'button formatted)))
    294   ;; If we have quotes around a key sequence, we should not propertize
    295   ;; it as the button styling will no longer be visible.
    296   (-let* ((emacs-major-version 28)
    297          (formatted (helpful--format-docstring "`\\[set-mark-command]'")))
    298     (should
    299      (string-equal formatted "C-SPC"))
    300     (should
    301      (eq
    302       (get-text-property 0 'face formatted)
    303       'help-key-binding)))
    304   (-let* ((emacs-major-version 27)
    305          (formatted (helpful--format-docstring "`\\[set-mark-command]'")))
    306     (should
    307      (string-equal formatted "C-SPC"))
    308     (should
    309      (eq
    310       (get-text-property 0 'face formatted)
    311       'button))))
    312 
    313 (ert-deftest helpful--format-docstring-mode-maps ()
    314   "Ensure we propertize references to keymaps."
    315   (-let [formatted (helpful--format-docstring "\\{python-mode-map}")]
    316     (should
    317      (s-contains-p "run-python" formatted)))
    318   ;; Handle non-existent mode maps gracefully.
    319   (-let [formatted (helpful--format-docstring "\\{no-such-mode-map}")]
    320     (should
    321      (s-contains-p "not currently defined" formatted))))
    322 
    323 (ert-deftest helpful--format-docstring--info ()
    324   "Ensure we propertize references to the info manual."
    325   ;; This is the typical format.
    326   (let* ((formatted (helpful--format-docstring "Info node `(elisp)foo'"))
    327          (paren-position (s-index-of "(" formatted)))
    328     (should
    329      (string-equal formatted "Info node (elisp)foo"))
    330     (should
    331      (get-text-property paren-position 'button formatted)))
    332   ;; Some functions, such as `signal', use 'anchor'.
    333   (let* ((formatted (helpful--format-docstring "Info anchor `(elisp)foo'"))
    334          (paren-position (s-index-of "(" formatted)))
    335     (should
    336      (string-equal formatted "Info anchor (elisp)foo"))
    337     (should
    338      (get-text-property paren-position 'button formatted)))
    339   ;; Ensure we handle wrapped lines too, e.g. in `org-odt-pixels-per-inch'.
    340   (let* ((formatted (helpful--format-docstring "Info node `(elisp)foo \nbar'"))
    341          (paren-position (s-index-of "(" formatted)))
    342     (should
    343      (string-equal formatted "Info node (elisp)foo \nbar"))
    344     (should
    345      (get-text-property paren-position 'button formatted)))
    346   ;; Some docstrings use "info" (lowercase).
    347   (let* ((formatted (helpful--format-docstring "info node `(elisp)foo'"))
    348          (paren-position (s-index-of "(" formatted)))
    349     (should
    350      (string-equal formatted "info node (elisp)foo"))
    351     (should
    352      (get-text-property paren-position 'button formatted)))
    353   ;; Some docstrings use angular quotation marks.
    354   (let* ((formatted (helpful--format-docstring "Info node ‘(elisp)foo’"))
    355          (paren-position (s-index-of "(" formatted)))
    356     (should
    357      (string-equal formatted "Info node (elisp)foo"))
    358     (should
    359      (get-text-property paren-position 'button formatted)))
    360   ;; If there's no manual information, assume it is part of the Emacs manual.
    361   (let* ((formatted (helpful--format-docstring "Info node ‘foo’"))
    362          (paren-position (s-index-of "(" formatted)))
    363     (should
    364      (string-equal formatted "Info node (emacs)foo"))
    365     (should
    366      (get-text-property paren-position 'button formatted))))
    367 
    368 (ert-deftest helpful--format-docstring--url ()
    369   "Ensure we propertize URLs with backticks."
    370   (let* ((formatted (helpful--format-docstring "URL `http://example.com'"))
    371          (url-position (s-index-of "h" formatted)))
    372     (should
    373      (string-equal formatted "URL http://example.com"))
    374     (should
    375      (get-text-property url-position 'button formatted))))
    376 
    377 (ert-deftest helpful--format-docstring--bare-url ()
    378   "Ensure we propertize URLs without backticks."
    379   (let* ((formatted (helpful--format-docstring "http://example.com\nbar"))
    380          (url-position (s-index-of "h" formatted)))
    381     (should
    382      (string-equal formatted "http://example.com\nbar"))
    383     (should
    384      (get-text-property url-position 'button formatted))
    385     (should
    386      (equal
    387       (get-text-property url-position 'url formatted)
    388       "http://example.com")))
    389   ;; Don't consider trailing punctuation to be part of the URL.
    390   (let* ((formatted (helpful--format-docstring "See http://example.com."))
    391          (url-position (s-index-of "h" formatted)))
    392     (should
    393      (string-equal formatted "See http://example.com."))
    394     (should
    395      (equal
    396       (get-text-property url-position 'url formatted)
    397       "http://example.com")))
    398   ;; Format markdown-style links.
    399   (let* ((formatted (helpful--format-docstring "See <http://example.com>."))
    400          (url-position (s-index-of "h" formatted)))
    401     (should
    402      (equal
    403       (get-text-property url-position 'url formatted)
    404       "http://example.com"))))
    405 
    406 (ert-deftest helpful--definition-c-vars ()
    407   "Handle definitions of variables in C source code."
    408   (skip-unless find-function-C-source-directory)
    409   (helpful--definition 'default-directory nil))
    410 
    411 (ert-deftest helpful--definition-special-form ()
    412   "Ensure we find the position of special forms."
    413   (skip-unless find-function-C-source-directory)
    414   (-let [(buf pos _)
    415          (helpful--definition 'if t)]
    416     (should buf)
    417     (should pos)))
    418 
    419 (setq helpful-var-without-defvar 'foo)
    420 
    421 (ert-deftest helpful--definition-no-defvar ()
    422   "Ensure we don't crash on calling `helpful--definition' on
    423 variables defined without `defvar'."
    424   (helpful--definition 'helpful-var-without-defvar nil))
    425 
    426 (ert-deftest helpful--definition-buffer-opened ()
    427   "Ensure we mark buffers as opened for variables."
    428   (require 'python)
    429   ;; This test will fail if you already have python.el.gz open in your
    430   ;; Emacs instance.
    431   (skip-unless (null (get-buffer "python.el.gz")))
    432 
    433   (-let [(buf _pos opened) (helpful--definition 'python-indent-offset nil)]
    434     (should (bufferp buf))
    435     (should opened)))
    436 
    437 (ert-deftest helpful--definition-edebug-fn ()
    438   "Ensure we use the position information set by edebug, if present."
    439   ;; Test with both edebug enabled and disabled. The edebug property
    440   ;; on the symbol varies based on this.
    441   (dolist (edebug-on (list nil t))
    442     (let ((edebug-all-forms edebug-on)
    443           (edebug-all-defs edebug-on))
    444       (with-temp-buffer
    445         (insert "(defun test-foo-edebug-defn () 44)")
    446         (goto-char (point-min))
    447 	(cl-letf (((symbol-function #'message)
    448 		   (lambda (_format-string &rest _args))))
    449           (eval (eval-sexp-add-defvars (edebug-read-top-level-form)) t))
    450 
    451         (-let [(buf _pos _opened) (helpful--definition 'test-foo-edebug-defn t)]
    452           (should buf))))))
    453 
    454 (ert-deftest helpful--definition-defstruct ()
    455   "Ensure we find the position of struct functions."
    456   (-let [(buf pos _)
    457          (helpful--definition #'make-ert-test t)]
    458     (should buf)
    459     (should pos)))
    460 
    461 (ert-deftest helpful-variable ()
    462   "Smoke test for `helpful-variable'."
    463   (helpful-variable 'tab-width))
    464 
    465 (ert-deftest helpful-visit-reference ()
    466   "Smoke test for `helpful-visit-reference'."
    467   (helpful-function 'replace-regexp-in-string)
    468   (goto-char (point-min))
    469   ;; Move forward to the first reference.
    470   (while (not (get-text-property (point) 'helpful-pos))
    471     (forward-char 1))
    472   (helpful-visit-reference))
    473 
    474 (ert-deftest helpful--signature ()
    475   "Ensure that autoloaded functions are handled gracefully"
    476   (should
    477    (equal (helpful--signature 'some-unused-function)
    478           "(some-unused-function [Arg list not available until function definition is loaded.])")))
    479 
    480 (ert-deftest helpful--signature-space ()
    481   "Ensure that symbols with spaces are handled correctly."
    482   (should
    483    (equal (helpful--signature 'helpful-test-fn-with\ space)
    484           "(helpful-test-fn-with\\ space)")))
    485 
    486 (ert-deftest helpful--signature--advertised ()
    487   "Ensure that we respect functions that declare `advertised-calling-convention'."
    488   (should
    489    (equal (helpful--signature 'start-process-shell-command)
    490           "(start-process-shell-command NAME BUFFER COMMAND)")))
    491 
    492 (ert-deftest helpful-function--single-buffer ()
    493   "Ensure that calling `helpful-buffer' does not leave any extra
    494 buffers lying around."
    495   (let ((initial-buffers (buffer-list))
    496         expected-buffers results-buffer)
    497     (helpful-function #'enable-theme)
    498     (setq results-buffer (get-buffer "*helpful command: enable-theme*"))
    499     (setq expected-buffers
    500           (cons results-buffer
    501                 initial-buffers))
    502     (should
    503      (null
    504       (-difference (buffer-list) expected-buffers)))))
    505 
    506 (ert-deftest helpful--kind-name ()
    507   (should
    508    (equal
    509     (helpful--kind-name 'message nil)
    510     "variable"))
    511   (should
    512    (equal
    513     (helpful--kind-name 'message t)
    514     "function"))
    515   (should
    516    (equal
    517     (helpful--kind-name 'save-excursion t)
    518     "special form")))
    519 
    520 (ert-deftest helpful--pretty-print ()
    521   ;; Strings should be formatted with double-quotes.
    522   (should (equal "\"foo\"" (helpful--pretty-print "foo")))
    523   ;; Don't crash on large plists using keywords.
    524   (helpful--pretty-print
    525    '(:foo foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo :bar bar)))
    526 
    527 (ert-deftest helpful-update-after-killing-buf ()
    528   "If we originally looked at a variable in a specific buffer,
    529 and that buffer has been killed, handle it gracefully."
    530   ;; Don't crash if the underlying buffer has been killed.
    531   (let (helpful-buf)
    532     (with-temp-buffer
    533       (helpful-variable 'tab-width)
    534       (setq helpful-buf (current-buffer)))
    535     (with-current-buffer helpful-buf
    536       (helpful-update))))
    537 
    538 (ert-deftest helpful--canonical-symbol ()
    539   (should
    540    (eq (helpful--canonical-symbol 'not t)
    541        'null))
    542   (should
    543    (eq (helpful--canonical-symbol 'search-forward-regexp t)
    544        're-search-forward))
    545   (should
    546    (eq (helpful--canonical-symbol 'emacs-bzr-version nil)
    547        'emacs-repository-version)))
    548 
    549 (ert-deftest helpful--aliases ()
    550   (should
    551    (equal (helpful--aliases 'null t)
    552           (list 'not)))
    553   (should
    554    (equal (helpful--aliases 'emacs-repository-version nil)
    555           (list 'emacs-bzr-version))))
    556 
    557 (defun helpful-fn-in-elc ())
    558 
    559 (ert-deftest helpful--elc-only ()
    560   "Ensure we handle functions where we have the .elc but no .el
    561 file."
    562   ;; Pretend that we've loaded `helpful-fn-in-elc' from /tmp/foo.elc.
    563   (let ((load-history (cons '("/tmp/foo.elc" (defun . helpful-fn-in-elc))
    564                             load-history)))
    565     ;; This should not error.
    566     (helpful-function 'helpful-fn-in-elc)))
    567 
    568 (ert-deftest helpful--unnamed-func ()
    569   "Ensure we handle unnamed functions too.
    570 
    571 This is important for `helpful-key', where a user may have
    572 associated a lambda with a keybinding."
    573   (let* ((fun (lambda (x) x))
    574          (buf (helpful--buffer fun t)))
    575     ;; There's no name, so just show lambda in the buffer name.
    576     (should
    577      (equal (buffer-name buf) "*helpful lambda*"))
    578     ;; Don't crash when we show the buffer.
    579     (with-current-buffer buf
    580       (helpful-update))))
    581 
    582 (ert-deftest helpful--unnamed-func-with-docstring ()
    583   (let* ((fun (lambda (x) "Foo" x))
    584          (buf (helpful--buffer fun t)))
    585     ;; Don't crash when we show the buffer.
    586     (with-current-buffer buf
    587       (helpful-update))))
    588 
    589 (ert-deftest helpful--unnamed-compiled-func ()
    590   "Ensure we handle unnamed byte-compiled functions.
    591 
    592 This is important for `helpful-key', where a user may have
    593 associated a lambda with a keybinding."
    594   (let* ((fun (byte-compile (lambda (x) x)))
    595          (buf (helpful--buffer fun t)))
    596     ;; There's no name, so just show lambda in the buffer name.
    597     (should
    598      (equal (buffer-name buf) "*helpful lambda*"))
    599     ;; Don't crash when we show the buffer.
    600     (with-current-buffer buf
    601       (helpful-update))))
    602 
    603 (ert-deftest helpful--obsolete-variable ()
    604   "Test display of obsolete variable."
    605   (let* ((var 'helpful-test-var-obsolete)
    606          (info (helpful--format-obsolete-info var nil)))
    607     (should
    608      (equal info "This variable is obsolete since 23.1; use helpful-test-var instead."))))
    609 
    610 (ert-deftest helpful--obsolete-function ()
    611   "Test display of obsolete function."
    612   (let* ((fun 'helpful-test-fun-obsolete)
    613          (info (helpful--format-obsolete-info fun t)))
    614     (should
    615      (equal info "This function is obsolete since 1.2.3; use helpful instead."))))
    616 
    617 (ert-deftest helpful--keymap-keys--sparse ()
    618   (let* ((parent-keymap (make-sparse-keymap))
    619          (keymap (make-sparse-keymap)))
    620     (set-keymap-parent keymap parent-keymap)
    621     (define-key parent-keymap (kbd "a") #'forward-char)
    622     (define-key keymap (kbd "C-c C-M-a") #'backward-char)
    623     (define-key keymap [remap quoted-insert] #'forward-line)
    624     (should
    625      (equal
    626       (helpful--keymap-keys keymap)
    627       '(([17] forward-line)
    628         ([3 27 1] backward-char)
    629         ([97] forward-char))))))
    630 
    631 (defvar helpful--dummy-keymap
    632   (let ((keymap (make-sparse-keymap)))
    633     (define-key keymap (kbd "a") #'forward-char)
    634     keymap))
    635 
    636 ;; `fset' is necessary for keymaps as prefixes. This is a quirky Emacs
    637 ;; API: https://emacs.stackexchange.com/q/28576/304
    638 (fset 'helpful--dummy-keymap helpful--dummy-keymap)
    639 
    640 (ert-deftest helpful--keymap-keys--prefix ()
    641   "Test we flatten keymaps with prefix keys."
    642   (let* ((keymap (make-sparse-keymap)))
    643     (define-key keymap (kbd "C-c") 'helpful--dummy-keymap)
    644     (should
    645      (equal
    646       (helpful--keymap-keys keymap)
    647       '(([3 97] forward-char))))))
    648 
    649 (ert-deftest helpful--keymap-keys ()
    650   (let* ((parent-keymap (make-keymap))
    651          (keymap (make-keymap)))
    652     (set-keymap-parent keymap parent-keymap)
    653     (define-key parent-keymap (kbd "a") #'forward-char)
    654     (define-key keymap (kbd "C-c C-M-a") #'backward-char)
    655     (define-key keymap [remap quoted-insert] #'forward-line)
    656     (should
    657      (equal
    658       ;; This order differs from a sparse keymap. We should fix that
    659       ;; if it makes any difference.
    660       (helpful--keymap-keys keymap)
    661       '(([3 27 1] backward-char)
    662         ([17] forward-line)
    663         ([97] forward-char))))))
    664 
    665 (ert-deftest helpful--keymap-keys--strings ()
    666   "Test that we handle maps with format (TYPE ITEM-NAME . BINDING)."
    667   ;; This is an actual piece of smerge-mode-map.
    668   (let ((keymap '(keymap (3 keymap
    669                             (94 keymap
    670                                 (61 keymap
    671                                     (61 "upper-lower" . smerge-diff-upper-lower)
    672                                     (62 "base-lower" . smerge-diff-base-lower)
    673                                     (60 "base-upper" . smerge-diff-base-upper)
    674                                     "Diff"))))))
    675     (should
    676      (equal
    677       (helpful--keymap-keys keymap)
    678       '(([3 94 61 61] smerge-diff-upper-lower)
    679         ([3 94 61 62] smerge-diff-base-lower)
    680         ([3 94 61 60] smerge-diff-base-upper))))))
    681 
    682 (ert-deftest helpful--keymap-keys--anonymous-fns ()
    683   (let* ((keymap (make-keymap)))
    684     (define-key keymap (kbd "a")
    685       (lambda () (interactive) (message "")))
    686     (define-key keymap (kbd "a")
    687       (byte-compile (lambda () (interactive) (message ""))))
    688 
    689     ;; Don't crash on anonymous functions in a keymap.
    690     (helpful--keymap-keys keymap)))
    691 
    692 (ert-deftest helpful--format-keymap--keyboard-macros ()
    693   (let* ((keymap (make-keymap)))
    694     ;; A keyboard macro can be a string or a vector.
    695     (define-key keymap "a" "ABC")
    696     (define-key keymap "b" [TAB])
    697 
    698     (should
    699      (equal
    700       (helpful--format-keymap keymap)
    701       "a  Keyboard Macro\nb  Keyboard Macro"))))
    702 
    703 (defun helpful--dummy-command ()
    704   (interactive))
    705 
    706 (ert-deftest helpful--keymaps-containing ()
    707   "Ensure that we find keymaps for variables with bindings."
    708   ;; This is defined in the global map.
    709   (should
    710    (helpful--keymaps-containing #'where-is))
    711 
    712   ;; Only defined in `minor-mode-map-alist'.
    713   (let ((keymap (make-sparse-keymap)))
    714     (define-key keymap (kbd "a") #'helpful--dummy-command)
    715     (let ((minor-mode-map-alist
    716            (cons (cons 'foo-mode keymap) minor-mode-map-alist)))
    717       (should
    718        (helpful--keymaps-containing #'helpful--dummy-command))))
    719 
    720   ;; Don't crash if there are dodgy values in `minor-mode-map-alist'.
    721   (let ((minor-mode-map-alist
    722          ;; I'm not convinced this is legal, but
    723          ;; pdf-cache-prefetch-minor-mode in pdf-tools has t as a
    724          ;; keymap.
    725          (cons (cons 'foo-mode t) minor-mode-map-alist)))
    726     (helpful--keymaps-containing #'helpful--dummy-command))
    727 
    728   ;; Create a keybinding that is very unlikely to clobber actually
    729   ;; defined keybindings in the current emacs instance.
    730   (global-set-key (kbd "C-c M-S-c") #'helpful--dummy-command)
    731 
    732   ;; This command should only be in `global-map' and
    733   ;; `mode-specific-map'.
    734   (should
    735    (equal
    736     (length (helpful--keymaps-containing #'helpful--dummy-command))
    737     (if (< emacs-major-version 30)
    738         2
    739       3)))
    740 
    741   ;; Undo keybinding.
    742   (global-set-key (kbd "C-c M-S-c") nil)
    743 
    744   ;; Check for ido command remapping.
    745   (ido-mode 1)
    746   (should
    747    (equal
    748     (helpful--keymaps-containing 'ido-find-file)
    749     '(("minor-mode-map-alist (ido-mode)" "<open>" "C-x C-f"))))
    750   (ido-mode 0))
    751 
    752 (defalias 'helpful--dummy-command-alias #'helpful--dummy-command)
    753 
    754 (ert-deftest helpful--keymaps-containing-aliases ()
    755   "Ensure that we find keymaps that we've bound command aliases
    756 in."
    757   ;; Create keybindings that are very unlikely to clobber actually
    758   ;; defined keybindings in the current emacs instance.
    759   (global-set-key (kbd "C-c M-S-c") #'helpful--dummy-command)
    760   (global-set-key (kbd "C-c M-S-d") #'helpful--dummy-command-alias)
    761 
    762   (unwind-protect
    763       (let* ((keymaps (helpful--keymaps-containing-aliases #'helpful--dummy-command (helpful--aliases 'helpful--dummy-command t)))
    764              (global-keybindings (cdr (assoc "global-map" keymaps))))
    765         (should
    766          (equal global-keybindings (list "C-c M-S-c" "C-c M-S-d"))))
    767 
    768     ;; Undo keybindings.
    769     (global-set-key (kbd "C-c M-S-c") nil)
    770     (global-set-key (kbd "C-c M-S-d") nil)))
    771 
    772 (ert-deftest helpful--merge-alists ()
    773   (should
    774    (equal
    775     (helpful--merge-alists '((a . (1 2 3)) (b . (4)))
    776                            '((a . (10)) (c . (11))))
    777     '((a . (1 2 3 10))
    778       (b . (4))
    779       (c . (11))))))
    780 
    781 (ert-deftest helpful--source ()
    782   (-let* (((buf pos _opened) (helpful--definition #'helpful--source t))
    783           (source (helpful--source #'helpful--source t buf pos)))
    784     (should
    785      (s-starts-with-p "(defun " source))))
    786 
    787 (ert-deftest helpful--source-c-fn ()
    788   (-let* (((buf pos _opened) (helpful--definition 'mode-line-format nil))
    789           (source (helpful--source 'mode-line-format nil buf pos)))
    790     (should
    791      (s-starts-with-p "DEFVAR" (s-trim-left source)))))
    792 
    793 (ert-deftest helpful--source-autoloaded ()
    794   "We should include the autoload cookie."
    795   (-let* (((buf pos _opened) (helpful--definition #'helpful-at-point t))
    796           (source (helpful--source #'helpful-at-point t buf pos)))
    797     (should
    798      (s-starts-with-p ";;;###autoload" source))))
    799 
    800 (ert-deftest helpful--source--interactively-defined-fn ()
    801   "We should return the raw sexp for functions where we can't
    802 find the source code."
    803   (eval '(defun test-foo-defined-interactively () 42))
    804   (-let* (((buf pos _opened) (helpful--definition #'test-foo-defined-interactively t)))
    805     (should
    806      (not
    807       (null
    808        (helpful--source #'test-foo-defined-interactively t buf pos))))))
    809 
    810 (ert-deftest helpful--outer-sexp ()
    811   ;; If point is in the middle of a form, we should return its position.
    812   (with-temp-buffer
    813     (insert "(foo bar baz)")
    814     (goto-char (point-min))
    815     (search-forward "b")
    816 
    817     (-let [(pos subforms)
    818            (helpful--outer-sexp (current-buffer) (point))]
    819       (should
    820        (equal pos (point-min)))
    821       (should
    822        (equal subforms '(foo bar)))))
    823   ;; If point is at the beginning of a form, we should still return its position.
    824   (with-temp-buffer
    825     (insert "(foo) (bar)")
    826     (goto-char (point-min))
    827     (search-forward "b")
    828     (backward-char 2)
    829 
    830     (-let [(pos subforms)
    831            (save-excursion
    832              (helpful--outer-sexp (current-buffer) (point)))]
    833       (should
    834        (equal pos (point)))
    835       (should
    836        (equal subforms '(bar))))))
    837 
    838 (ert-deftest helpful--summary--aliases ()
    839   ;; exclude the sym itself
    840   "Ensure we mention that a symbol is an alias."
    841   (-let* (((buf pos opened) (helpful--definition '-select t))
    842           (summary (helpful--summary '-select t buf pos)))
    843     (when opened
    844       (kill-buffer buf))
    845     ;; Strip properties to make assertion messages more readable.
    846     (set-text-properties 0 (1- (length summary)) nil summary)
    847     (should
    848      (equal
    849       summary
    850       "-select is a function alias for -filter, defined in dash.el."))))
    851 
    852 (ert-deftest helpful--summary--special-form ()
    853   "Ensure we describe special forms correctly"
    854   (-let* ((summary (helpful--summary 'if t nil nil)))
    855     ;; Strip properties to make assertion messages more readable.
    856     (set-text-properties 0 (1- (length summary)) nil summary)
    857     (should
    858      (s-starts-with-p "if is a special form defined in" summary))))
    859 
    860 (defun helpful-test-fn-interactive ()
    861   (interactive))
    862 
    863 (ert-deftest helpful--summary--interactive-fn ()
    864   "Ensure we use \"an\" for interactive functions."
    865   (let* ((summary (helpful--summary 'helpful-test-fn-interactive t nil nil)))
    866     ;; Strip properties to make assertion messages more readable.
    867     (set-text-properties 0 (1- (length summary)) nil summary)
    868     (should
    869      (s-starts-with-p "helpful-test-fn-interactive is an interactive function" summary))))
    870 
    871 (defun helpful-test-fn? ()
    872   (interactive))
    873 
    874 (ert-deftest helpful--summary--fn-with-? ()
    875   "Ensure we use don't needlessly escape ? in function names."
    876   (let* ((summary (helpful--summary 'helpful-test-fn? t nil nil)))
    877     ;; Strip properties to make assertion messages more readable.
    878     (set-text-properties 0 (1- (length summary)) nil summary)
    879     (should
    880      (s-starts-with-p "helpful-test-fn? is" summary))))
    881 
    882 (ert-deftest helpful--signature-fn-with? ()
    883   "Ensure that symbols with question marks are handled correctly."
    884   (should
    885    (equal (helpful--signature 'helpful-test-fn?)
    886           "(helpful-test-fn?)")))
    887 
    888 (defun helpful-test-fn-with\ space ()
    889   42)
    890 
    891 (ert-deftest helpful--summary--symbol-with-space ()
    892   "Ensure we correctly format symbols containing spaces."
    893   (let* ((summary (helpful--summary 'helpful-test-fn-with\ space t nil nil)))
    894     ;; Strip properties to make assertion messages more readable.
    895     (set-text-properties 0 (1- (length summary)) nil summary)
    896     (should
    897      (s-starts-with-p "helpful-test-fn-with\\ space is a function" summary))))
    898 
    899 (ert-deftest helpful--bound-p ()
    900   ;; Functions.
    901   (should (helpful--bound-p 'message))
    902   ;; Variables
    903   (should (helpful--bound-p 'tab-width))
    904   ;; Unbound.
    905   (should (not (helpful--bound-p 'this-variable-does-not-exist)))
    906   ;; For our purposes, we don't consider nil or t to be bound.
    907   (should (not (helpful--bound-p 'nil)))
    908   (should (not (helpful--bound-p 't))))
    909 
    910 (ert-deftest helpful--callees ()
    911   (should
    912    (equal
    913     (helpful--callees '(quote (foo)))
    914     nil))
    915   ;; Simple function calls.
    916   (should
    917    (equal
    918     (helpful--callees '(foo (bar 1) 2))
    919     '(foo bar))))
    920 
    921 (ert-deftest helpful--callees-let ()
    922   (should
    923    (equal
    924     (helpful--callees
    925      '(progn
    926         (let ((x (foo))
    927               (y t))
    928           (bar x y))
    929         (let (y)
    930           (baz))
    931         (let* ((z (quux))))))
    932     '(foo bar baz quux))))
    933 
    934 (ert-deftest helpful--callees--lambda ()
    935   (should
    936    (equal
    937     (helpful--callees '(lambda (x) (foo x)))
    938     '(foo))))
    939 
    940 (ert-deftest helpful--callees--closure ()
    941   (should
    942    (equal
    943     (helpful--callees '(closure (t) (x) (foo x)))
    944     '(foo))))
    945 
    946 (ert-deftest helpful--callees--function ()
    947   (should
    948    (equal
    949     (helpful--callees '(function (lambda (x) (foo x))))
    950     '(foo)))
    951   (should
    952    (equal
    953     (helpful--callees '(function foo))
    954     '(foo))))
    955 
    956 (ert-deftest helpful--callees--cond ()
    957   (should
    958    (equal
    959     (helpful--callees
    960      '(cond
    961        (x)
    962        ((foo))
    963        ((bar)
    964         (baz))
    965        (t
    966         (quux))
    967        ))
    968     '(foo bar baz quux))))
    969 
    970 (ert-deftest helpful--callees--condition-case ()
    971   (should
    972    (equal
    973     (helpful--callees
    974      '(condition-case e
    975           (foo)
    976         (error (bar))
    977         ((arith-error file-error) (baz))))
    978     '(foo bar baz))))
    979 
    980 (ert-deftest helpful--callees--funcall ()
    981   (let ((result (helpful--callees
    982                  '(progn
    983                     (funcall 'foo 1)
    984                     (apply 'bar 2)
    985                     (apply (baz) 3)
    986                     (apply unknown-var 3)))))
    987     (should (memq 'foo result))
    988     (should (memq 'bar result))
    989     (should (memq 'baz result))
    990     (should (not (memq 'unknown-var result))))
    991   (let ((result (helpful--callees
    992                  '(progn
    993                     (funcall #'foo 1)
    994                     (apply #'bar 2)))))
    995     (should (memq 'foo result))
    996     (should (memq 'bar result))))
    997 
    998 (ert-deftest helpful--callees-button--smoke ()
    999   (with-temp-buffer
   1000     (let ((button (helpful--make-callees-button
   1001                    'whatever
   1002                    '(defun whatever () (something) (test 5)))))
   1003       (insert button)
   1004       (goto-char (point-min))
   1005       (push-button)))
   1006   (with-temp-buffer
   1007     (let ((button (helpful--make-callees-button
   1008                    '(lambda () (interactive) (other-window -1))
   1009                    '(lambda () (interactive) (other-window -1)))))
   1010       (insert button)
   1011       (goto-char (point-min))
   1012       (push-button))))
   1013 
   1014 ;; TODO: broken when byte-compiling helpful.el.
   1015 ;; (ert-deftest helpful--autoloaded-p ()
   1016 ;;   (-let [(buf pos opened) (helpful--definition 'rx-to-string t)]
   1017 ;;     (should (helpful--autoloaded-p 'rx-to-string buf))
   1018 ;;     (when opened
   1019 ;;       (kill-buffer buf))))
   1020 
   1021 (ert-deftest helpful--inhibit-read-only ()
   1022   (helpful-variable 'inhibit-read-only)
   1023   (should
   1024    (s-contains-p "Value\nnil" (buffer-string))))
   1025 
   1026 (ert-deftest helpful--convert-c-name ()
   1027   (should
   1028    (equal
   1029     'make-string
   1030     (helpful--convert-c-name 'Fmake_string nil)))
   1031   (should
   1032    (equal
   1033     'gc-cons-percentage
   1034     (helpful--convert-c-name 'Vgc_cons_percentage t)))
   1035   (should-not
   1036    (helpful--convert-c-name 'Fmake_string t))
   1037   (should-not
   1038    (helpful--convert-c-name 'Vgc_cons_percentage nil)))
   1039 
   1040 (ert-deftest helpful-symbol-c-style ()
   1041   (helpful-symbol 'Fget_char_property)
   1042   (helpful-symbol 'Vinhibit_field_text_motion))
   1043 
   1044 (ert-deftest helpful-symbol-unbound ()
   1045   "Ensure we inform the user if we're given an unbound symbol."
   1046   (should
   1047    (condition-case _
   1048        (helpful-symbol 'notboundtoanything)
   1049      ('user-error t))))
   1050 
   1051 (ert-deftest helpful--loads-autoload-symbol ()
   1052   "When asked to describe an autoloaded symbol, just load it."
   1053   ;; This test assumes that you haven't loaded tetris.el.gz in your
   1054   ;; current instance.
   1055   (skip-unless (autoloadp (symbol-function 'tetris)))
   1056   ;; This is a regression test: `tetris' has `tetris-mode-map' in its
   1057   ;; docstring, so we can't display the mode map unless tetris.el.gz is
   1058   ;; loaded.
   1059   ;;
   1060   (helpful-function #'tetris))
   1061 
   1062 (defcustom helpful-test-custom-var 123
   1063   "I am an example custom variable."
   1064   :type 'number
   1065   :group 'helpful
   1066   :package-version '(helpful . "1.2.3"))
   1067 
   1068 ;; Ensure the current value differs from the original value.
   1069 (setq helpful-test-custom-var 456)
   1070 
   1071 (ert-deftest helpful--original-value ()
   1072   "Show the original value for defcustom variables."
   1073   (helpful-variable 'helpful-test-custom-var)
   1074   (should
   1075    (s-contains-p "Original Value\n123" (buffer-string))))
   1076 
   1077 (ert-deftest helpful--preserve-position ()
   1078   "Show the original value for defcustom variables."
   1079   (-let [(buf _pos _opened) (helpful--definition 'helpful-test-custom-var nil)]
   1080     (with-current-buffer buf
   1081       (goto-char (point-min))
   1082       (save-current-buffer
   1083         (helpful-variable 'helpful-test-custom-var))
   1084       (should (eq (point) (point-min))))))
   1085 
   1086 (ert-deftest helpful--package-version ()
   1087   "Report when a variable was added"
   1088   (helpful-variable 'helpful-test-custom-var)
   1089   (should
   1090    (s-contains-p
   1091     (s-word-wrap
   1092      70
   1093      "This variable was added, or its default value changed, in helpful version 1.2.3.")
   1094     (buffer-string))))
   1095 
   1096 (ert-deftest helpful--display-implementations ()
   1097   (require 'xref)
   1098   (helpful-function 'xref-location-marker)
   1099   (should (s-contains-p "Implementations" (buffer-string)))
   1100   (should (if (version< emacs-version "29.1")
   1101 	      (s-contains-p "((l xref-file-location))" (buffer-string))
   1102 	    (s-contains-p "(xref-location-marker (L xref-file-location))" (buffer-string))))
   1103   (should (if (version< emacs-version "29.1")
   1104 	      (s-contains-p "((l xref-buffer-location))" (buffer-string))
   1105 	    (s-contains-p "(xref-location-marker (L xref-buffer-location))" (buffer-string)))))
   1106 
   1107 (defun helpful--boring-advice (orig-fn &rest args)
   1108   (apply orig-fn args))
   1109 
   1110 (advice-add 'ruby-mode :around #'helpful--boring-advice)
   1111 
   1112 (ert-deftest helpful--autoload-functions-with-advice ()
   1113   "Ensure that we can describe an autoloaded function
   1114 that has advice attached before it is loadedl."
   1115   (helpful-function 'ruby-mode))
   1116 
   1117 (ert-deftest helpful--tree-any-p ()
   1118   (should (helpful--tree-any-p (lambda (x) (eq x 1)) '((((1))))))
   1119   (should (helpful--tree-any-p (lambda (x) (eq x 1)) (cons 2 1))))