Friday, July 16, 2010

Emacs, mozrepl and javascript autotesting

Emacs, mozrepl and javascript autotesting

This article shows how to use mozrepl in conjunction with emacs to run tests (or some other function) automatically in a web page in your browser as you work on a related javascript file in emacs.

This is not some perfect setup. And given how quickly things change these days it may be hard to replicate. But it shows the power of several different technologies interacting to give us more freedom in what we do and how we do it.

  • To follow this blog post, you need to:
    • install mozrepl
      • mozrepl is a firefox addon; there is an older version called mozlab; you should go here instead though where you should find a link for the .xpi file
      • after restarting firefox, you should be able to telnet to the js repl
          bash> telnet localhost 4242
          ... [header stuff]...
          repl1>
        
        • this is a nice little repl; you can experiment with javascript here and test things out; you might want to check out the tutorial
        • of course, doing it in a telnet program isn't so much fun; you could wrap it in readline library such as:
          bash> rlwrap telnet localhost 4242
          ... [header stuff]...
          repl1>
          
        • this should give you similar functionality that you'd expect on the bash prompt
        • of course, there's arguably a better solution still, which is to run it in emacs since this will allow you to
          • edit javascript files and send parts or all thereof to mozrepl directly, and
          • generate command strings dynamically in emacs and send these directly to mozrepl
      • which leads us to mozrepl.el ...
    • install mozrepl.el in your emacs
      • instructions are here
      • I should note that the javascript mode I'm using in emacs comes from here
      • and my mozrepl.el comes from another emacs mode: nxhtml mode
      • these were some additional setup lines in my .emacs
          (add-to-list 'auto-mode-alist '("\.js$" . js-mode))
          (autoload 'inferior-moz-mode "moz" "MozRepl Inferior Mode" t)
          (autoload 'moz-minor-mode "moz" "MozRepl Minor Mode" t)
          (add-hook 'js-mode-hook 'javascript-moz-setup)
          (defun javascript-moz-setup ()
            (interactive)
            (moz-minor-mode 1))
        
      • at the moment, javascript development in emacs seems to be in a state of flux with several js/javascript mode versions floating around; getting set up can be confusing and is beyond what I am trying to share here
      • you should be able to perform this command in your emacs (or something like it)
        M-x run-mozilla
        

        and have a new buffer open up with a repl prompt similar to the telnet experience above.

Putting it together

My set up has the following features:

  • I use a home-grown test framework called unitjs that allows me to group my tests into sections, and group these sections into modules (functions) that I can run either when the page loads, or on clicking a button (etc)
  • I have an html page for testing that loads my js project files and the testing framework I am using to test the project as well as the tests themselves
  • these tests are not run automatically but sit in functions (which I loosely refer to as modules)
  • for instance, I might have
    var tests = {};
    ...
    tests.module1 = function() {
      ... do some setup ...
      ... define some test functions with assertions ...
      ... call the test runner to run the tests above and collect the results ...
    }
    
  • each module corresponds to a separate js file in the tests folder of my project
    • this is important for the setup I am demonstrating as will be shown shortly
  • to run the tests for module1, for instance, you just need to invoke the function
    tests.module1()
    

    and the results will get displayed on the test page

  • similarly for tests.module2 and so on...

Hooking up emacs, mozrepl and autotesting

What we're going to do now is get emacs to automagically work out which test module file we have just saved and send the command (tests.moduleX()) to mozrepl, which will then execute this command within the context of our test page, thereby running and displaying the tests

  • in your .emacs file we set up a function that does the job of reloading the page and sending the test instructions:

      (defun me/mozrepl/run-after-reload (str)
       "Send string to mozrepl, but only after reloading"
       (interactive)
       (progn
         (comint-send-string (inferior-moz-process) "repl.home();")
         (comint-send-string (inferior-moz-process) "BrowserReload();")
         (sleep-for 0 500)
         (comint-send-string (inferior-moz-process) "repl.enter(content);")
         (comint-send-string
          (inferior-moz-process) str )))
    
  • [18-Jul-2010]: I've added ';' after each js command in the above defun

  • all we're doing here is sending a series of commands via the repl
    • each comint-send-string command is as though we were typing that command into the repl ourselves
  • note that the commands reload the browser page, then get mozrepl to enter into the context of the document (repl.enter(content)); this gives us access to our javascript code that is loaded on this page
    • you can check by typing into the repl the name of one of your global variables in your javascript project; the repl should have access to it and you should be able to use it
    • when I was playing with mozrepl this wasn't at all obvious and I almost gave up trying to access the javascript that had loaded with my page until I discovered this step
  • once we're in the correct context, we can get the repl to evaluate 'str'
  • note the sleep command; I'm not sure how important this is but I thought it prudent (and may have had trouble otherwise) to give the browser some time to reload before trying to send another command to the repl

    • there may be other ways to do this such as not reloading the browser page and simple insert a script tag into the head node of the document using DOM operations
  • now we add in some functionality that will examine the buffer we are writing our tests in and guesses which test function to send to the repl:

        (defun proj1/tests/run (test-module)
          "Pass test invocation string to me/mozrepl/run-after-reload
          ; eg test-module might be 'tests.module1'
          ; command will be: tests.module1(document.getElementById('tests-container'))"
          (me/mozrepl/run-after-reload
           (concat test-module "(document.getElementById('tests-container'))")))
    
        (defun proj1/find-and-run-test-module ()
          "Look for test module signature; if found, send it to proj1/tests/run"
          (interactive)
          (cond
           ((search "tests.module1 = function" (buffer-string))
            (proj1/tests/run "tests.module1"))
           ((search "tests.module2 = function" (buffer-string))
            (proj1/tests/run "tests.module2")) ))
    
  • the first defun takes a test module name generates the particular command I want mozrepl to evaluate within the context of the testing page:

    "tests.module1(document.getElementById('tests-container'))"
    
    • will be evaluated by the repl within the context of my testing page and will run the module1 tests and pass in a reference to a test-container node (on the testing page) that the results will be displayed in
  • the second defun searches the current buffer and looks for a matching piece of text using the elisp (search ...) function ; in this case, the first part of our definition of the test module is used

    • if it is found, then we invoke the first defun with the relevant module
  • finally, we don't want to have to run M-x proj1/find-and-run-test-module when we are in our test file, so we set up a save hook to do it automatically:

        (defun proj1/autotest () (interactive)
          (add-hook 'after-save-hook 'proj1/find-and-run-test-module 'append 'local))  
    
        (defun proj1/autotest-remove () (interactive)
          (remove-hook 'after-save-hook 'proj1/find-and-run-test-module 'local))
    
  • we invoke the first defun [M-x proj1/autotest] in our buffer just once; thereafter whenever we save, our test page will get reloaded and the test module for that page gets executed

  • the second defun allows us to turn this off
  • And that's pretty much it!

Notes

  • Don't forget:
    • you need to keep your browser open on the testing page with the loaded code
    • You will need to start the repl in emacs (M-x run-mozilla)
  • I haven't figured out how to navigate tabs in mozrepl

Concluding Remarks

  • mozrepl allows you to tinker with live webpages via a javascript repl
  • emacs is great at connecting with repls in general
  • this article was just experimenting with combining these two

Related pages

  • The inspiration for this artice was this page on emacswiki
    • this page shows how to program emacs to send commands to mozrepl
    • it highlights comint mode; one of the things that makes emacs an extremely powerful, versatile editor and which we used above

Friday, April 2, 2010

Learning emacs - programming emacs with elisp - I

I'm learning emacs and wanted to blog about how to script it and make it do things to enhance your productivity. The stuff I'm going to do here is really one step up from being a reasonably proficient user of emacs if that. I'm really only just getting used to lisp and emacs version of it — called elisp.

There is an opportunity here, though. By getting to know emacs better you will have to learn elisp. And by learning elisp, you are really learning a fully-capable programming language and a dialect of lisp. So you get to kill 2 birds with one stone: you get to know one of the most extensible text editors and one of the most extensible programming languages - it's no coincidence the two are related.

Warm up - evaluating some elisp

  • If you're new to emacs, see this link: Getting help in emacs
  • Load up emacs
  • head over to the *scratch* buffer
  • Let's look at one of the most basic operations in emacs: moving the cursor forward. How do we find what function is involved? Type: C-h k C-f . "C-h k" invokes the help function in emacs that is responsible for telling you what a key combination does; the key combination we asked it to look up was "C-f" - which is emacs default way to move the cursor forward. On my emacs, this is bound to "(forward-char &optional n)"[1]. What this means is that it is bound to the function "forward-char" which takes an optional argument "n".
  • So, on a new line in *scratch* type: (forward-char 2) 1234567890 Leave no spaces between your text and the left edge of the buffer.
  • Now put your cursor right at the end of the line with forward-char: ("|" is the position of the cursor below)
                (forward-char 2)|
                1234567890
              
    ... and type C-x C-e
    You cursor should move to here:
                (forward-char 2)
                1|234567890
              
    Try changing 2 to 1 or -1 and see what happens.
    NOTE: always put your cursor right to the end of the line before you type C-x C-e. One easy way to do this is to type C-e before you type C-x C-e
  • What you've done is execute your first lisp program.
  • Another way you could have executed 'forward-char' is via emacs' M-x key; type M-x forward-char and press RETURN and follow the prompts.
  • What you should note is that any function that we run in emacs can be called using S-expressions (using lisp-parlance). Something like this
                (some-function arg1 arg2 ...)
              
    Which in more "mainstream" languages would look more like:
                some_function(arg1,arg2,...)
              
    This is how lisp and scheme work in general. The reasons for surrounding things in brackets runs quite deep. If you want to get into it, google "cons cell", "homoiconic" or just "lisp". For my purposes I'm just showing how to get started programmatically in emacs in this article.

Here's a great little series on lisp programming by James Turner. There are at least 4 parts.

[1] - if you are in viper-mode, you should go into insert mode (press i) and use the emacs key bindings to move around eg C-e and C-x C-e will work in insert mode (my viper experience level is set to 5 so your mileage may vary if you are using a lower level).