diff options
Diffstat (limited to 'yasnippet-tests.el')
-rw-r--r-- | yasnippet-tests.el | 801 |
1 files changed, 653 insertions, 148 deletions
diff --git a/yasnippet-tests.el b/yasnippet-tests.el index 6b7a91a..6048467 100644 --- a/yasnippet-tests.el +++ b/yasnippet-tests.el @@ -1,6 +1,6 @@ ;;; yasnippet-tests.el --- some yasnippet tests -*- lexical-binding: t -*- -;; Copyright (C) 2012, 2013, 2014, 2015 Free Software Foundation, Inc. +;; Copyright (C) 2012-2015, 2017-2018 Free Software Foundation, Inc. ;; Author: João Távora <joaot@siscog.pt> ;; Keywords: emulations, convenience @@ -22,12 +22,134 @@ ;; Test basic snippet mechanics and the loading system +;; To test this in emacs22 mac osx: +;; curl -L -O https://github.com/mirrors/emacs/raw/master/lisp/emacs-lisp/ert.el +;; curl -L -O https://github.com/mirrors/emacs/raw/master/lisp/emacs-lisp/ert-x.el +;; /usr/bin/emacs -nw -Q -L . -l yasnippet-tests.el --batch -e ert + ;;; Code: (require 'yasnippet) (require 'ert) (require 'ert-x) (require 'cl-lib) +(require 'org) + + +;;; Helper macros and function + +(defmacro yas-with-snippet-dirs (dirs &rest body) + (declare (indent defun) (debug t)) + `(yas-call-with-snippet-dirs + ,dirs #'(lambda () ,@body))) + +(defun yas-should-expand (keys-and-expansions) + (dolist (key-and-expansion keys-and-expansions) + (yas-exit-all-snippets) + (erase-buffer) + (insert (car key-and-expansion)) + (ert-simulate-command '(yas-expand)) + (unless (string= (yas--buffer-contents) (cdr key-and-expansion)) + (ert-fail (format "\"%s\" should have expanded to \"%s\" but got \"%s\"" + (car key-and-expansion) + (cdr key-and-expansion) + (yas--buffer-contents))))) + (yas-exit-all-snippets)) + +(defun yas--collect-menu-items (menu-keymap) + (let ((yas--menu-items ())) + (map-keymap (lambda (_binding definition) + (when (eq (car-safe definition) 'menu-item) + (push definition yas--menu-items))) + menu-keymap) + yas--menu-items)) + +(defun yas-should-not-expand (keys) + (dolist (key keys) + (yas-exit-all-snippets) + (erase-buffer) + (insert key) + (ert-simulate-command '(yas-expand)) + (unless (string= (yas--buffer-contents) key) + (ert-fail (format "\"%s\" should have stayed put, but instead expanded to \"%s\"" + key + (yas--buffer-contents)))))) + +(defun yas-mock-insert (string) + (dotimes (i (length string)) + (let ((last-command-event (aref string i))) + (ert-simulate-command '(self-insert-command 1))))) + +(defun yas-mock-yank (string) + (let ((interprogram-paste-function (lambda () string))) + (ert-simulate-command '(yank nil)))) + +(defun yas--key-binding (key) + "Like `key-binding', but override `this-command-keys-vector'. +This lets `yas--maybe-expand-from-keymap-filter' work as expected." + (cl-letf (((symbol-function 'this-command-keys-vector) + (lambda () (cl-coerce key 'vector)))) + (key-binding key))) + +(defun yas-make-file-or-dirs (ass) + (let ((file-or-dir-name (car ass)) + (content (cdr ass))) + (cond ((listp content) + (make-directory file-or-dir-name 'parents) + (let ((default-directory (concat default-directory "/" file-or-dir-name))) + (mapc #'yas-make-file-or-dirs content))) + ((stringp content) + (with-temp-buffer + (insert content) + (write-region nil nil file-or-dir-name nil 'nomessage))) + (t + (message "[yas] oops don't know this content"))))) + + +(defun yas-variables () + (let ((syms)) + (mapatoms #'(lambda (sym) + (if (and (string-match "^yas-[^/]" (symbol-name sym)) + (boundp sym)) + (push sym syms)))) + syms)) + +(defun yas-call-with-saving-variables (fn) + (let* ((vars (yas-variables)) + (saved-values (mapcar #'symbol-value vars))) + (unwind-protect + (funcall fn) + (cl-loop for var in vars + for saved in saved-values + do (set var saved))))) + +(defun yas-call-with-snippet-dirs (dirs fn) + (let* ((default-directory (make-temp-file "yasnippet-fixture" t)) + (yas-snippet-dirs (mapcar (lambda (d) (expand-file-name (car d))) dirs))) + (with-temp-message "" + (unwind-protect + (progn + (mapc #'yas-make-file-or-dirs dirs) + (funcall fn)) + (when (>= emacs-major-version 24) + (delete-directory default-directory 'recursive)))))) + +;;; Older emacsen +;;; +(unless (fboundp 'special-mode) + ;; FIXME: Why provide this default definition here?!? + (defalias 'special-mode 'fundamental)) + +(unless (fboundp 'string-suffix-p) + ;; introduced in Emacs 24.4 + (defun string-suffix-p (suffix string &optional ignore-case) + "Return non-nil if SUFFIX is a suffix of STRING. +If IGNORE-CASE is non-nil, the comparison is done without paying +attention to case differences." + (let ((start-pos (- (length string) (length suffix)))) + (and (>= start-pos 0) + (eq t (compare-strings suffix nil nil + string start-pos nil ignore-case)))))) ;;; Snippet mechanics @@ -90,6 +212,15 @@ (should (string= (yas--buffer-contents) (concat filled-words "\n")))))) +(ert-deftest auto-fill-with-multiparagraph () + "Test auto-fill protection on snippet spanning multiple paragraphs" + (with-temp-buffer + (yas-minor-mode +1) + (auto-fill-mode +1) + (yas-expand-snippet "foo$1\n\n$2bar") + (yas-mock-insert " ") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (looking-at "bar")))) (ert-deftest primary-field-transformation () (with-temp-buffer @@ -143,9 +274,20 @@ (ert-simulate-command '(yas-next-field-or-maybe-expand)) (should (looking-at "testblable")) (ert-simulate-command '(yas-next-field-or-maybe-expand)) - (ert-simulate-command '(yas-skip-and-clear-or-delete-char)) + (ert-simulate-command '(yas-skip-and-clear-field)) (should (looking-at "ble")) - (should (null (yas--snippets-at-point))))) + (should (null (yas-active-snippets))))) + +(ert-deftest delete-nested-simple-field-issue-824 () + "Test deleting a field with a nested simple field in it." + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${3:so-$4and}$0${2:-so}") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (looking-at "so-and-so")) + (ert-simulate-command '(yas-skip-and-clear-or-delete-char)) + (should (looking-at "-so")) + (should (null (yas-active-snippets))))) (ert-deftest ignore-trailing-whitespace () (should (equal @@ -166,6 +308,76 @@ ;; (should (string= (yas--buffer-contents) ;; "brother from another mother!")))) +(ert-deftest undo-redo () + "Check redoing of snippet undo." + (yas-with-snippet-dirs '((".emacs.d/snippets" + ("emacs-lisp-mode" ("x" . "${1:one},and done")))) + (with-temp-buffer + (emacs-lisp-mode) + (yas-reload-all) + (yas-minor-mode 1) + (yas-expand-snippet "x$0") + (let ((pre-expand-string (buffer-string))) + (setq buffer-undo-list nil) + (ert-simulate-command '(yas-expand)) + (push nil buffer-undo-list) + (ert-simulate-command '(yas-next-field)) ; $1 -> exit snippet. + (should (string-match-p "\\`one,and done" (buffer-string))) + (push nil buffer-undo-list) + (ert-simulate-command '(undo)) ; Revive snippet. + (ert-simulate-command '(undo)) ; Undo expansion. + (should (string= (buffer-string) pre-expand-string)) + (ert-simulate-command '(move-end-of-line 1)) + (push nil buffer-undo-list) + (ert-simulate-command '(undo)) ; Redo (re-expand snippet). + (should (string-match-p "\\`one,and done" (buffer-string))))))) + +(defun yas-test-expand-and-undo (mode snippet-entry initial-contents) + (yas-with-snippet-dirs + `((".emacs.d/snippets" (,(symbol-name mode) ,snippet-entry))) + (with-temp-buffer + (funcall mode) + (yas-reload-all) + (yas-minor-mode 1) + (yas-expand-snippet initial-contents) + (let ((pre-expand-string (buffer-string))) + (setq buffer-undo-list ()) + (ert-simulate-command '(yas-expand)) + ;; Need undo barrier, I think command loop puts it normally. + (push nil buffer-undo-list) + (ert-simulate-command '(undo)) + (should (string= (buffer-string) pre-expand-string)))))) + +(ert-deftest undo-indentation-1 () + "Check undoing works when only line of snippet is indented." + (let ((yas-also-auto-indent-first-line t)) + (yas-test-expand-and-undo + 'emacs-lisp-mode '("s" . "(setq $0)") "(let\n(while s$0"))) + +(ert-deftest undo-indentation-2 () + "Check undoing works when only line of snippet is indented." + (let ((yas-also-auto-indent-first-line t) + (indent-tabs-mode nil)) + (yas-test-expand-and-undo + 'emacs-lisp-mode '("t" . "; TODO") "t$0"))) + +(ert-deftest undo-indentation-multiline-1 () + "Check undoing works when 1st line of multi-line snippet is indented." + (let ((yas-also-auto-indent-first-line t) + (indent-tabs-mode nil)) + (yas-test-expand-and-undo + 'js-mode '("if" . "if ($1) {\n\n}\n") + "if$0\nabc = 123456789 + abcdef;"))) + + +(ert-deftest undo-indentation-multiline-2 () + "Check undoing works when 2nd line of multi-line snippet is indented." + (let ((yas-also-auto-indent-first-line t) + (indent-tabs-mode nil)) + (yas-test-expand-and-undo + 'js-mode '("if" . "if (true) {\n${1:foo};\n}\n") + "if$0\nabc = 123456789 + abcdef;"))) + (ert-deftest dont-clear-on-partial-deletion-issue-515 () "Ensure fields are not cleared when user doesn't really mean to." (with-temp-buffer @@ -218,6 +430,47 @@ end" (buffer-string))) end" (buffer-string))) (should (= 4 (current-column))))) +(ert-deftest yas-also-indent-empty-lines () + "Respect `yas-also-indent-empty-lines' setting." + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) 'auto) + (set (make-local-variable 'yas-also-auto-indent-first-line) t) + (set (make-local-variable 'yas-also-indent-empty-lines) t) + (yas-expand-snippet "def foo\n\nend") + (should (string= "def foo\n \nend" (buffer-string))) + ;; Test that it keeps working without setting + ;; `yas-also-auto-indent-first-line'. + (setq yas-also-auto-indent-first-line nil) + (erase-buffer) + (yas-expand-snippet "def foo\n\nend") + (should (string= "def foo\n \nend" (buffer-string))))) + +(ert-deftest yas-indent-first-line () + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) 'auto) + (set (make-local-variable 'yas-also-auto-indent-first-line) nil) + (set (make-local-variable 'yas-also-indent-empty-lines) nil) + (yas-expand-snippet "def foo\n$0\nend\n") + ;; First (and only) line should not indent. + (yas-expand-snippet "#not indented") + (should (equal "def foo\n#not indented\nend\n" (buffer-string))))) + +(ert-deftest yas-indent-first-line-fixed () + (with-temp-buffer + (ruby-mode) + (yas-minor-mode 1) + (set (make-local-variable 'yas-indent-line) 'fixed) + (set (make-local-variable 'yas-also-auto-indent-first-line) nil) + (set (make-local-variable 'yas-also-indent-empty-lines) nil) + (yas-expand-snippet " def foo\n $0\n end\n") + ;; First (and only) line should not indent. + (yas-expand-snippet "#not more indented") + (should (equal " def foo\n #not more indented\n end\n" (buffer-string))))) + (ert-deftest indentation-markers () "Test a snippet with indentation markers (`$<')." (with-temp-buffer @@ -243,6 +496,78 @@ $1 ------------------------") XXXXX ---------------- XXXXX ---- XXXXX ------------------------")))) +(ert-deftest single-line-multi-mirror-indentation-2 () + "Like `single-line-multi-mirror-indentation' but 2 mirrors interleaved." + ;; See also Github issue #768. + (with-temp-buffer + (c-mode) + (yas-minor-mode 1) + (yas-expand-snippet "${1:one} ${2:two};\n$1 $2_;\n$2 $1_;\n") + (should (string= (yas--buffer-contents) + "one two;\none two_;\ntwo one_;\n")))) + +(ert-deftest indent-org-property () + "Handling of `org-mode' property indentation, see `org-property-format'." + ;; This is an interesting case because `org-indent-line' calls + ;; `replace-match' for properties. + (with-temp-buffer + (org-mode) + (yas-minor-mode +1) + (yas-expand-snippet "* Test ${1:test}\n:PROPERTIES:\n:ID: $1-after\n:END:") + (yas-mock-insert "foo bar") + (ert-simulate-command '(yas-next-field)) + (goto-char (point-min)) + (let ((expected (with-temp-buffer + (insert (format (concat "* Test foo bar\n" + " " org-property-format "\n" + " " org-property-format "\n" + " " org-property-format) + ":PROPERTIES:" "" + ":ID:" "foo bar-after" + ":END:" "")) + (delete-trailing-whitespace) + (buffer-string)))) + ;; Some org-mode versions leave trailing whitespace, some don't. + (delete-trailing-whitespace) + (should (equal expected (buffer-string)))))) + +(ert-deftest indent-cc-mode () + "Handling of cc-mode's indentation." + ;; This is an interesting case because cc-mode deletes all the + ;; indentation before recreating it. + (with-temp-buffer + (c++-mode) + (yas-minor-mode +1) + (yas-expand-snippet "\ +int foo() +{ + if ($1) { + delete $1; + $1 = 0; + } +}") + (yas-mock-insert "var") + (should (string= "\ +int foo() +{ + if (var) { + delete var; + var = 0; + } +}" (buffer-string))))) + +(ert-deftest indent-snippet-mode () + "Handling of snippet-mode indentation." + ;; This is an interesting case because newlines match [[:space:]] in + ;; snippet-mode. + (with-temp-buffer + (snippet-mode) + (yas-minor-mode +1) + (yas-expand-snippet "# -*- mode: snippet -*-\n# name: $1\n# key: $1\n# --\n") + (yas-mock-insert "foo") + (should (string= "# -*- mode: snippet -*-\n# name: foo\n# key: foo\n# --\n" + (buffer-string))))) + (ert-deftest indent-mirrors-on-update () "Check that mirrors are always kept indented." (with-temp-buffer @@ -351,6 +676,43 @@ mapconcat #'(lambda (arg) (yas-expand-snippet "Look ma! ${1:`(yas-selected-text)`} OK?") (should (string= (yas--buffer-contents) "Look ma! He)}o world! OK?"))))) +(ert-deftest insert-snippet-with-backslashes-in-active-field () + ;; This test case fails if `yas--inhibit-overlay-hooks' is not bound + ;; in `yas-expand-snippet' (see Github #844). + (with-temp-buffer + (yas-minor-mode 1) + (yas-expand-snippet "${1:$$(if (not yas-modified-p) \"a\")}") + (yas-expand-snippet "\\\\alpha"))) + +(ert-deftest expand-with-unused-yas-selected-text () + (with-temp-buffer + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "expanded `yas-selected-text`foo")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should (equal (buffer-string) "expanded foo"))))) + +(ert-deftest yas-expand-command-snippet () + (with-temp-buffer + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "\ +# type: command +# -- +\(insert \"expanded foo\")")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should (equal (buffer-string) "expanded foo"))))) + (ert-deftest example-for-issue-271 () (with-temp-buffer (yas-minor-mode 1) @@ -361,6 +723,17 @@ mapconcat #'(lambda (arg) (yas-mock-insert "bbb") (should (string= (yas--buffer-contents) "if condition\naaa\nelse\nbbb\nend"))))) +(ert-deftest yas-no-memory-of-bad-snippet () + "Expanding an incorrect snippet should not influence future expansions." + ;; See https://github.com/joaotavora/yasnippet/issues/800. + (with-temp-buffer + (yas-minor-mode 1) + (should-error (yas-expand-snippet "```foo\n\n```")) + (erase-buffer) ; Bad snippet may leave wrong text. + ;; But expanding the corrected snippet should work fine. + (yas-expand-snippet "\\`\\`\\`foo\n\n\\`\\`\\`") + (should (equal (buffer-string) "```foo\n\n```")))) + (defmacro yas--with-font-locked-temp-buffer (&rest body) "Like `with-temp-buffer', but ensure `font-lock-mode'." (declare (indent 0) (debug t)) @@ -382,14 +755,9 @@ mapconcat #'(lambda (arg) (kill-buffer ,temp-buffer)))))))) (defmacro yas-saving-variables (&rest body) + (declare (debug t)) `(yas-call-with-saving-variables #'(lambda () ,@body))) -(defmacro yas-with-snippet-dirs (dirs &rest body) - (declare (indent defun)) - `(yas-call-with-snippet-dirs ,dirs - #'(lambda () - ,@body))) - (ert-deftest example-for-issue-474 () (yas--with-font-locked-temp-buffer (c-mode) @@ -480,6 +848,88 @@ TODO: correct this bug!" "brother from another mother") ;; no newline should be here! ))) +(defvar yas-tests--ran-exit-hook nil) + +(ert-deftest snippet-exit-hooks () + (with-temp-buffer + (yas-saving-variables + (let ((yas-tests--ran-exit-hook nil) + (yas-triggers-in-field t)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "\ +# expand-env: ((yas-after-exit-snippet-hook (lambda () (setq yas-tests--ran-exit-hook t)))) +# -- +FOO ${1:f1} ${2:f2}") + ("sub" . "\ +# expand-env: ((yas-after-exit-snippet-hook (lambda () (setq yas-tests--ran-exit-hook 'sub)))) +# -- +SUB")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should-not yas-tests--ran-exit-hook) + (yas-mock-insert "sub") + (ert-simulate-command '(yas-expand)) + (ert-simulate-command '(yas-next-field)) + (should-not yas-tests--ran-exit-hook) + (ert-simulate-command '(yas-next-field)) + (should (eq yas-tests--ran-exit-hook t))))))) + +(ert-deftest snippet-exit-hooks-bindings () + "Check that `yas-after-exit-snippet-hook' is handled correctly +in the case of a buffer-local variable and being overwritten by +the expand-env field." + (with-temp-buffer + (yas-saving-variables + (let ((yas-tests--ran-exit-hook nil) + (yas-triggers-in-field t) + (yas-after-exit-snippet-hook nil)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "foobar\n") + ("baz" . "\ +# expand-env: ((yas-after-exit-snippet-hook (lambda () (setq yas-tests--ran-exit-hook 'letenv)))) +# -- +foobaz\n")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (add-hook 'yas-after-exit-snippet-hook (lambda () (push 'global yas-tests--ran-exit-hook))) + (add-hook 'yas-after-exit-snippet-hook (lambda () (push 'local yas-tests--ran-exit-hook)) nil t) + (insert "baz") + (ert-simulate-command '(yas-expand)) + (should (eq 'letenv yas-tests--ran-exit-hook)) + (insert "foo") + (ert-simulate-command '(yas-expand)) + (should (eq 'global (nth 0 yas-tests--ran-exit-hook))) + (should (eq 'local (nth 1 yas-tests--ran-exit-hook)))))))) + +(ert-deftest snippet-mirror-bindings () + "Check that variables defined with the expand-env field are +accessible from mirror transformations." + (with-temp-buffer + (yas-saving-variables + (let ((yas-triggers-in-field t) + (yas-good-grace nil)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("baz" . "\ +# expand-env: ((func #'upcase)) +# -- +hello ${1:$(when (stringp yas-text) (funcall func yas-text))} foo${1:$$(concat \"baz\")}$0")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (insert "baz") + (ert-simulate-command '(yas-expand)) + (should (string= (yas--buffer-contents) "hello BAZ foobaz\n"))))))) + (defvar yas--barbaz) (defvar yas--foobarbaz) @@ -523,11 +973,69 @@ TODO: correct this bug!" (yas-should-expand '(("foo-barbaz" . "OKfoo-barbazOK") ("foo " . "foo ")))))))) +(ert-deftest nested-snippet-expansion-1 () + (with-temp-buffer + (yas-minor-mode +1) + (let ((yas-triggers-in-field t)) + (yas-expand-snippet "Parent $1 Snippet") + (yas-expand-snippet "(Child $1 $2 Snippet)") + (let ((snippets (yas-active-snippets))) + (should (= (length snippets) 2)) + (should (= (length (yas--snippet-fields (nth 0 snippets))) 2)) + (should (= (length (yas--snippet-fields (nth 1 snippets))) 1)))))) + +(ert-deftest nested-snippet-expansion-2 () + (let ((yas-triggers-in-field t)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("text-mode" + ("nest" . "one($1:$1) two($2).$0")))) + (yas-reload-all) + (text-mode) + (yas-minor-mode +1) + (insert "nest") + (ert-simulate-command '(yas-expand)) + (yas-mock-insert "nest") + (ert-simulate-command '(yas-expand)) + (yas-mock-insert "x") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "y") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "z") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (string= (buffer-string) + "one(one(x:x) two(y).:one(x:x) two(y).) two(z)."))))) + +(ert-deftest nested-snippet-expansion-3 () + (let ((yas-triggers-in-field t)) + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("text-mode" + ("rt" . "\ +\\sqrt${1:$(if (string-equal \"\" yas/text) \"\" \"[\")}${1:}${1:$(if (string-equal \"\" yas/text) \"\" \"]\")}{$2}$0")))) + (yas-reload-all) + (text-mode) + (yas-minor-mode +1) + (insert "rt") + (ert-simulate-command '(yas-expand)) + (yas-mock-insert "3") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "rt") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "5") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (yas-mock-insert "2") + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (ert-simulate-command '(yas-next-field-or-maybe-expand)) + (should (string= (buffer-string) "\\sqrt[3]{\\sqrt[5]{2}}"))))) + ;;; Loading ;;; (defmacro yas-with-overriden-buffer-list (&rest body) + (declare (debug t)) (let ((saved-sym (make-symbol "yas--buffer-list"))) `(let ((,saved-sym (symbol-function 'buffer-list))) (cl-letf (((symbol-function 'buffer-list) @@ -540,6 +1048,7 @@ TODO: correct this bug!" (defmacro yas-with-some-interesting-snippet-dirs (&rest body) + (declare (debug t)) `(yas-saving-variables (yas-with-overriden-buffer-list (yas-with-snippet-dirs @@ -562,11 +1071,28 @@ TODO: correct this bug!" "Test `yas-lookup-snippet'." (yas-with-some-interesting-snippet-dirs (yas-reload-all 'no-jit) - (should (equal (yas-lookup-snippet "printf" 'c-mode) "printf($1);")) - (should (equal (yas-lookup-snippet "def" 'c-mode) "# define")) + (should (equal (yas--template-content (yas-lookup-snippet "printf" 'c-mode)) + "printf($1);")) + (should (equal (yas--template-content (yas-lookup-snippet "def" 'c-mode)) + "# define")) (should-not (yas-lookup-snippet "no such snippet" nil 'noerror)) (should-not (yas-lookup-snippet "printf" 'emacs-lisp-mode 'noerror)))) +(ert-deftest yas-lookup-snippet-with-env () + (with-temp-buffer + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("emacs-lisp-mode" + ("foo" . "\ +# expand-env: ((foo \"bar\")) +# -- +`foo`")))) + (yas-reload-all) + (emacs-lisp-mode) + (yas-minor-mode +1) + (yas-expand-snippet (yas-lookup-snippet "foo")) + (should (equal (buffer-string) "bar"))))) + (ert-deftest basic-jit-loading () "Test basic loading and expansion of snippets" (yas-with-some-interesting-snippet-dirs @@ -594,13 +1120,15 @@ TODO: correct this bug!" (with-temp-buffer (text-mode) (yas-minor-mode +1) - (should (equal (yas-lookup-snippet "one") "one")) - (should (eq (key-binding "\C-c1") 'yas-expand-from-keymap)) + (should (equal (yas--template-content (yas-lookup-snippet "one")) + "one")) + (should (eq (yas--key-binding "\C-c1") 'yas-expand-from-keymap)) (yas-define-snippets 'text-mode '(("_1" "one!" "won" nil nil nil nil nil "uuid-1"))) (should (null (yas-lookup-snippet "one" nil 'noerror))) - (should (null (key-binding "\C-c1"))) - (should (equal (yas-lookup-snippet "won") "one!"))))) + (should (null (yas--key-binding "\C-c1"))) + (should (equal (yas--template-content(yas-lookup-snippet "won")) + "one!"))))) (ert-deftest snippet-save () "Make sure snippets can be saved correctly." @@ -620,13 +1148,13 @@ TODO: correct this bug!" (yas-minor-mode +1) (save-current-buffer (yas-new-snippet t) - (with-current-buffer "*new snippet*" + (with-current-buffer yas-new-snippet-buffer-name (snippet-mode) (insert "# name: foo\n# key: bar\n# --\nsnippet foo") (call-interactively 'yas-load-snippet-buffer-and-close))) (save-current-buffer (yas-new-snippet t) - (with-current-buffer "*new snippet*" + (with-current-buffer yas-new-snippet-buffer-name (snippet-mode) (insert "# name: bar\n# key: bar\n# --\nsnippet bar") (call-interactively 'yas-load-snippet-buffer-and-close))) @@ -667,7 +1195,7 @@ TODO: correct this bug!" yet-another-c-mode and-also-this-one and-that-one - ;; prog-mode doesn't exist in emacs 24.3 + ;; prog-mode doesn't exist in emacs 23.4 ,@(if (fboundp 'prog-mode) '(prog-mode)) emacs-lisp-mode @@ -694,7 +1222,7 @@ TODO: correct this bug!" c-mode ,major-mode)) (expected-rest `(cc-mode - ;; prog-mode doesn't exist in emacs 24.3 + ;; prog-mode doesn't exist in emacs 23.4 ,@(if (fboundp 'prog-mode) '(prog-mode)) emacs-lisp-mode @@ -767,9 +1295,38 @@ TODO: correct this bug!" (yas-should-not-expand '("sc" "dolist" "ert-deftest")))) +;;; Unloading +(ert-deftest yas-unload () + "Test unloading and reloading." + (with-temp-buffer + (let ((status (call-process + (concat invocation-directory invocation-name) + nil '(t t) nil + "-Q" "--batch" "-L" yas--loaddir "-l" "yasnippet" + "--eval" + (prin1-to-string + '(condition-case err + (progn + (yas-minor-mode +1) + (unload-feature 'yasnippet) + ;; Unloading leaves `yas-minor-mode' bound, + ;; harmless, though perhaps surprising. + (when (bound-and-true-p yas-minor-mode) + (error "`yas-minor-mode' still enabled")) + (when (fboundp 'yas-minor-mode) + (error "`yas-minor-mode' still fboundp")) + (require 'yasnippet) + (unless (fboundp 'yas-minor-mode) + (error "Failed to reload"))) + (error (message "%S" (error-message-string err)) + (kill-emacs 1))))))) + (ert-info ((buffer-string)) (should (eq status 0)))))) + + ;;; Menu ;;; (defmacro yas-with-even-more-interesting-snippet-dirs (&rest body) + (declare (debug t)) `(yas-saving-variables (yas-with-snippet-dirs `((".emacs.d/snippets" @@ -805,16 +1362,16 @@ TODO: correct this bug!" (let ((yas-use-menu t)) (yas-with-even-more-interesting-snippet-dirs (yas-reload-all 'no-jit) - (let ((menu (cdr (gethash 'fancy-mode yas--menu-table)))) - (should (eql 4 (length menu))) + (let ((menu-items (yas--collect-menu-items + (gethash 'fancy-mode yas--menu-table)))) + (should (eql 4 (length menu-items))) (dolist (item '("a-guy" "a-beggar")) - (should (cl-find item menu :key #'cl-third :test #'string=))) - (should-not (cl-find "an-outcast" menu :key #'cl-third :test #'string=)) + (should (cl-find item menu-items :key #'cl-second :test #'string=))) + (should-not (cl-find "an-outcast" menu-items :key #'cl-second :test #'string=)) (dolist (submenu '("sirs" "ladies")) (should (keymapp - (cl-fourth - (cl-find submenu menu :key #'cl-third :test #'string=))))) - )))) + (cl-third + (cl-find submenu menu-items :key #'cl-second :test #'string=))))))))) (ert-deftest test-group-menus () "Test group-based menus using .yas-make-groups and the group directive" @@ -889,16 +1446,23 @@ TODO: be meaner" ;;; The infamous and problematic tab keybinding ;;; (ert-deftest test-yas-tab-binding () - (with-temp-buffer - (yas-minor-mode -1) - (should (not (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand))) - (yas-minor-mode 1) - (should (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand)) - (yas-expand-snippet "$1 $2 $3") - (should (eq (key-binding [(tab)]) 'yas-next-field-or-maybe-expand)) - (should (eq (key-binding (kbd "TAB")) 'yas-next-field-or-maybe-expand)) - (should (eq (key-binding [(shift tab)]) 'yas-prev-field)) - (should (eq (key-binding [backtab]) 'yas-prev-field)))) + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("fundamental-mode" + ("foo" . "foobar")))) + (yas-reload-all) + (with-temp-buffer + (yas-minor-mode -1) + (insert "foo") + (should (not (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand))) + (yas-minor-mode 1) + (should (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand)) + (yas-expand-snippet "$1 $2 $3") + (should (eq (key-binding [(tab)]) 'yas-next-field-or-maybe-expand)) + (should (eq (key-binding (kbd "TAB")) 'yas-next-field-or-maybe-expand)) + (should (eq (key-binding [(shift tab)]) 'yas-prev-field)) + (should (eq (key-binding [backtab]) 'yas-prev-field)))))) (ert-deftest test-rebindings () (let* ((yas-minor-mode-map (copy-keymap yas-minor-mode-map)) @@ -918,11 +1482,58 @@ TODO: be meaner" (should (eq (key-binding (kbd "SPC")) 'yas-expand))))) (ert-deftest test-yas-in-org () - (with-temp-buffer - (org-mode) - (yas-minor-mode 1) - (should (eq (key-binding [(tab)]) 'yas-expand)) - (should (eq (key-binding (kbd "TAB")) 'yas-expand)))) + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("org-mode" + ("foo" . "foobar")))) + (yas-reload-all) + (with-temp-buffer + (org-mode) + (yas-minor-mode 1) + (insert "foo") + (should (eq (key-binding [(tab)]) 'yas-expand)) + (should (eq (key-binding (kbd "TAB")) 'yas-expand)))))) + +(ert-deftest yas-org-native-tab-in-source-block () + "Test expansion of snippets in org source blocks." + :expected-result (if (and (fboundp 'org-in-src-block-p) (version< (org-version) "9")) + :passed :failed) + (yas-saving-variables + (yas-with-snippet-dirs + '((".emacs.d/snippets" + ("text-mode" + ("T" . "${1:one} $1\n${2:two} $2\n<<$0>> done!")))) + (let ((text-mode-hook '(yas-minor-mode)) + (org-src-tab-acts-natively t) + ;; Org 8.x requires this in order for + ;; `org-src-tab-acts-natively' to have effect. + (org-src-fontify-natively t)) + (yas-reload-all) + ;; Org relies on font-lock to identify source blocks. + (yas--with-font-locked-temp-buffer + (org-mode) + (yas-minor-mode 1) + (insert "#+BEGIN_SRC text\nT\n#+END_SRC") + (if (fboundp 'font-lock-ensure) + (font-lock-ensure) + (jit-lock-fontify-now)) + (re-search-backward "^T$") (goto-char (match-end 0)) + (should (org-in-src-block-p)) + (ert-simulate-command `(,(key-binding (kbd "TAB")))) + (ert-simulate-command `(,(key-binding (kbd "TAB")))) + (ert-simulate-command `(,(key-binding (kbd "TAB")))) + ;; Check snippet exit location. + (should (looking-at ">> done!")) + (goto-char (point-min)) + (forward-line) + ;; Check snippet expansion, ignore leading whitespace due to + ;; `org-edit-src-content-indentation'. + (should (looking-at "\ +[[:space:]]*one one +[[:space:]]*two two +[[:space:]]*<<>> done!"))))))) + (ert-deftest test-yas-activate-extra-modes () "Given a symbol, `yas-activate-extra-mode' should be able to @@ -948,116 +1559,10 @@ add the snippets associated with the given mode." (yas-should-expand '(("car" . "(car )"))))))) -;;; Helpers -;;; -(defun yas-should-expand (keys-and-expansions) - (dolist (key-and-expansion keys-and-expansions) - (yas-exit-all-snippets) - (erase-buffer) - (insert (car key-and-expansion)) - (let ((yas-fallback-behavior nil)) - (ert-simulate-command '(yas-expand))) - (unless (string= (yas--buffer-contents) (cdr key-and-expansion)) - (ert-fail (format "\"%s\" should have expanded to \"%s\" but got \"%s\"" - (car key-and-expansion) - (cdr key-and-expansion) - (yas--buffer-contents))))) - (yas-exit-all-snippets)) - -(defun yas-should-not-expand (keys) - (dolist (key keys) - (yas-exit-all-snippets) - (erase-buffer) - (insert key) - (let ((yas-fallback-behavior nil)) - (ert-simulate-command '(yas-expand))) - (unless (string= (yas--buffer-contents) key) - (ert-fail (format "\"%s\" should have stayed put, but instead expanded to \"%s\"" - key - (yas--buffer-contents)))))) - -(defun yas-mock-insert (string) - (dotimes (i (length string)) - (let ((last-command-event (aref string i))) - (ert-simulate-command '(self-insert-command 1))))) - -(defun yas-mock-yank (string) - (let ((interprogram-paste-function (lambda () string))) - (ert-simulate-command '(yank nil)))) - -(defun yas-make-file-or-dirs (ass) - (let ((file-or-dir-name (car ass)) - (content (cdr ass))) - (cond ((listp content) - (make-directory file-or-dir-name 'parents) - (let ((default-directory (concat default-directory "/" file-or-dir-name))) - (mapc #'yas-make-file-or-dirs content))) - ((stringp content) - (with-temp-buffer - (insert content) - (write-region nil nil file-or-dir-name nil 'nomessage))) - (t - (message "[yas] oops don't know this content"))))) - - -(defun yas-variables () - (let ((syms)) - (mapatoms #'(lambda (sym) - (if (and (string-match "^yas-[^/]" (symbol-name sym)) - (boundp sym)) - (push sym syms)))) - syms)) - -(defun yas-call-with-saving-variables (fn) - (let* ((vars (yas-variables)) - (saved-values (mapcar #'symbol-value vars))) - (unwind-protect - (funcall fn) - (cl-loop for var in vars - for saved in saved-values - do (set var saved))))) - -(defun yas-call-with-snippet-dirs (dirs fn) - (let* ((default-directory (make-temp-file "yasnippet-fixture" t)) - (yas-snippet-dirs (mapcar (lambda (d) (expand-file-name (car d))) dirs))) - (with-temp-message "" - (unwind-protect - (progn - (mapc #'yas-make-file-or-dirs dirs) - (funcall fn)) - (when (>= emacs-major-version 24) - (delete-directory default-directory 'recursive)))))) - -;;; Older emacsen -;;; -(unless (fboundp 'special-mode) - ;; FIXME: Why provide this default definition here?!? - (defalias 'special-mode 'fundamental)) - -(unless (fboundp 'string-suffix-p) - ;; introduced in Emacs 24.4 - (defun string-suffix-p (suffix string &optional ignore-case) - "Return non-nil if SUFFIX is a suffix of STRING. -If IGNORE-CASE is non-nil, the comparison is done without paying -attention to case differences." - (let ((start-pos (- (length string) (length suffix)))) - (and (>= start-pos 0) - (eq t (compare-strings suffix nil nil - string start-pos nil ignore-case)))))) - -;;; btw to test this in emacs22 mac osx: -;;; curl -L -O https://github.com/mirrors/emacs/raw/master/lisp/emacs-lisp/ert.el -;;; curl -L -O https://github.com/mirrors/emacs/raw/master/lisp/emacs-lisp/ert-x.el -;;; /usr/bin/emacs -nw -Q -L . -l yasnippet-tests.el --batch -e ert - - -(put 'yas-saving-variables 'edebug-form-spec t) -(put 'yas-with-snippet-dirs 'edebug-form-spec t) -(put 'yas-with-overriden-buffer-list 'edebug-form-spec t) -(put 'yas-with-some-interesting-snippet-dirs 'edebug-form-spec t) (provide 'yasnippet-tests) ;; Local Variables: ;; indent-tabs-mode: nil +;; autoload-compute-prefixes: nil ;; End: ;;; yasnippet-tests.el ends here |