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))))