aboutsummaryrefslogtreecommitdiff
path: root/test/helpful-unit-test.el
diff options
context:
space:
mode:
authorWilfred Hughes <me@wilfred.me.uk>2018-08-12 16:17:42 -0700
committerWilfred Hughes <me@wilfred.me.uk>2018-08-12 16:17:42 -0700
commit253fec8cccc7369f65bb314bdda01ee702efc725 (patch)
tree7ba1f6bbb1d39e0cffd2dd8365eb6dd40120c67d /test/helpful-unit-test.el
parent5568c780e1b609a18728c592c0f85d798b6a1a47 (diff)
Rename file to avoid confusion with other projects' unit tests0.13
Diffstat (limited to 'test/helpful-unit-test.el')
-rw-r--r--test/helpful-unit-test.el782
1 files changed, 782 insertions, 0 deletions
diff --git a/test/helpful-unit-test.el b/test/helpful-unit-test.el
new file mode 100644
index 0000000..7feb8a0
--- /dev/null
+++ b/test/helpful-unit-test.el
@@ -0,0 +1,782 @@
+(require 'ert)
+(require 'edebug)
+(require 'helpful)
+(require 'python)
+
+(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"))))
+
+(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-keymap ()
+ "Handle keymap references in docstrings."
+ (let* ((formatted-docstring
+ (helpful--format-docstring
+ "\\<minibuffer-local-map>\\[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-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))
+ (shut-up
+ (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-callable ()
+ ;; We should not crash when looking at macros.
+ (helpful-callable 'when)
+ ;; Special forms should work too.
+ (helpful-callable 'if)
+ ;; Smoke test for special forms when we have the Emacs C source
+ ;; loaded.
+ (let* ((emacs-src-path (f-join default-directory "emacs-25.3" "src")))
+ (if (f-exists-p emacs-src-path)
+ (let ((find-function-C-source-directory emacs-src-path))
+ (helpful-callable 'if))
+ (message "No Emacs source code found at %S, skipping test. Run ./download_emacs_src.sh."
+ emacs-src-path))))
+
+(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, 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 empy 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)))
+ ;; Propertize mode maps.
+ (-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))))
+
+(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 <http://example.com>."))
+ (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."
+ (let* ((emacs-src-path (f-join default-directory "emacs-25.3" "src")))
+ (if (f-exists-p emacs-src-path)
+ (let ((find-function-C-source-directory emacs-src-path))
+ (helpful--definition 'default-directory nil))
+ (message "No Emacs source code found at %S, skipping test. Run ./download_emacs_src.sh"
+ emacs-src-path))))
+
+(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.
+ (-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))
+ (shut-up
+ (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-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--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 '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--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)))
+
+(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)" "<open>" "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))
+ (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))))
+
+(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))))