4

I wrote a minor mode with the purpose of modifying written text on the fly.

It is currently creating a post-self-insert-command-hook, and triggers only on specific keypresses (e.g. the space bar, a semicolon, an open parens).

As an example, the user would type select and when they press the space key, the buffer would now contain SELECT.

I'm having a hard time writing a test for this with ert. I've tried writing it thus:

(ert-deftest upcase-select ()
  (with-temp-buffer
    (sqlup-mode)
    (insert "select")
    (execute-kbd-macro (kbd "SPC"))
    (should (equal (buffer-string) "SELECT "))))

This test should verify that when I press the space key, the word "select" becomes the word SELECT But the test ends up comparing " " and "SELECT ".

How can I write a test that triggers the behavior I desire?

(and further: is this a terrible idea? what's a better idea?)

Trevoke
  • 2,375
  • 21
  • 34
  • 2
    Not an answer, but it sounds like you're reimplementing abbrevs – Malabarba Dec 27 '14 at 21:45
  • I just took another look at this. It looks like `execute-kbd-macro` take pre- and post-command hooks into account. In other words, your code should work. Are you sure the underlying mode `sqlup-mode` work as intended? – Lindydancer Nov 01 '15 at 08:00

4 Answers4

3

My take on this is that execute-kbd-macro acts on the buffer of the selected window (it is effectively typed keystrokes from the user, after all), and you must therefore ensure that this is set to your temporary buffer.

e.g.:

(with-temp-buffer
  (insert "select")
  (save-window-excursion
    (set-window-buffer nil (current-buffer))
    (execute-kbd-macro (kbd "SPC")))
  (buffer-string))

I note that the same kind of thing is going on in the code of Lindydancer's answer.

phils
  • 48,657
  • 3
  • 76
  • 115
3

You can see how I solved a similar problem with lispy-test.el. Here's an example test:

(ert-deftest lispy-braces ()
  (should (string= (lispy-with "\"a regex \\\\|\"" "{")
                   "\"a regex \\\\{|\\\\}\""))
  (should (string= (lispy-with "\"a string |" "{")
                   "\"a string {|}")))

lispy-with is a macro that creates a temporary file with emacs-lisp-mode, inserts the first string, calls the command sequence decoded from the second string and returns the final buffer contents:

(defmacro lispy-with (in &rest body)
  `(with-temp-buffer
     (emacs-lisp-mode)
     (lispy-mode)
     (insert ,in)
     (when (search-backward "~" nil t)
       (delete-char 1)
       (set-mark (point))
       (goto-char (point-max)))
     (search-backward "|")
     (delete-char 1)
     ,@(mapcar (lambda (x) (if (stringp x) `(lispy-unalias ,x) x)) body)
     (insert "|")
     (when (region-active-p)
       (exchange-point-and-mark)
       (insert "~"))
     (buffer-substring-no-properties
      (point-min)
      (point-max))))
abo-abo
  • 13,943
  • 1
  • 29
  • 43
2

First, the idea is great, you should write this kind of test cases for your packages.

One way to do this is to insert a suitable content into a temporary buffer, add suitable keys to the input queue, break into recursive edit, and let nature have its course. This way, things like post-command hooks are executed.

For example:

(defun test-upcase-interactively ()
  (let ((unread-command-events
         (append
          (kbd "M-u")
          ;; Remove the following line to stay in recursive edit.
          (kbd "C-M-c")          ; Exit recursive edit.
          unread-command-events)))
    (with-temp-buffer
      (save-excursion
        (insert "words"))
      (save-window-excursion
        (select-window (display-buffer (current-buffer)))
        (recursive-edit))
      (buffer-substring (point-min) (point-max)))))



(ert-deftest my-test ()
  (should (equal (test-upcase-interactively)
           "WORDS")))

Of course, it's overkill to use the recursive edit trick for something like upcase-word, as it doesn't rely on pre- and post-commands.

Lindydancer
  • 6,095
  • 1
  • 13
  • 25
1

I've been using ecukes to write these sorts of tests and it's brilliant.

Test cases look like this:

  # Check *args works ok
  Scenario: Space *args on its own
    When I type "f(*args)"
    Then I should see "f(*args)"

  Scenario: Space *args with other args
    When I type "f(a,*args)"
    Then I should see "f(a, *args)"

  # And **kwargs
  Scenario: Space **kwargs on its own
    When I type "f(**kwargs)"
    Then I should see "f(**kwargs)"

  Scenario: Space **kwargs with other args
    When I type "f(a,**kwargs)"
    Then I should see "f(a, **kwargs)"

I can add more details on setting it up if needed, or you can look at the project I linked (the Cask file and the features directory are relevant to ecukes).

dshepherd
  • 1,281
  • 6
  • 18