Friday, June 28, 2013

baseapp: Other Grunt Goodies

baseapp provides all the boilerplate to get your JavaScript web application started off right, this Part 5.

  1. Intro to baseapp
  2. Client-Side Unit Tests
  3. Server-Side Unit Tests
  4. WebDriver Integration Tests
  5. Other Grunt Goodies
  6. Authentication
  7. Continuous Integration
  8. Administration
(or binge read them all on the baseapp wiki!)

baseapp provides some other grunt goodies as well beyond client-side unit tests, server-side unit tests, and integration tests! Let's take a look...

jshint

What JavaScript project would be complete with jslint/jshint? Here is the grunt configuration::

    jshint: {
        all: ['Gruntfile.js', 'public/javascripts/**/*.js', 'routes/**/*.js', 'spec/**/*.js', 'app.js']
        , options: {
            globals: {
                jQuery: true
            }
            , 'laxcomma': true
            , 'multistr': true
        }
    }

I like putting commas at the beginning of expressions and occasionally have been known to use multi-line strings - change these to suit you. Also I don't want jshint to complain about the 'jQuery' global variable. Finally I want to run jshint on my Gruntfile, all of my client-side JavaScript, server-side JavaScript, test files, and app.js. To use simply:

% grunt jshint

And jshint will complain loudly if it finds something it does not like. Note this task is the first task run as part of 'grunt test'.

Template Compilation

I like to pre-compile my dustjs-linkedin templates - who doesn't? This makes all of my templates available to be rendered by the client/browser as appropriate. To the grunt configuration!

    dustjs: {
        compile: {
            files: {
                "public/javascripts/templates.js": ["views/**/*.dust"]
            }
        }
    }

Not much here, this task will simply compile all the dust templates found in the 'view' tree and put them into 'templates.js', suitable for framing or loading into your HTML as you see fit:

    <script src="/vendor/dust-core-1.2.3.min.js"></script>
    <script src="/javascripts/templates.js"></script>

Note you need to of course also load up 'dust-core' to get the dust magic to actually fill out and render a template from templates.js. Here is a snippet from login.js that does this:

    dust.render('index', { user: data }, function(err, out){
        var newDoc = document.open("text/html", "replace");
        newDoc.write(out);
        newDoc.close();
    });

This is slightly different than a typical app as I am replacing the entire page once a user has successfully logged in - typically you just replace a piece of the page. Here I tell dust I want to render the 'index' template (compiled from 'index.dust') and am passing in some data to the template (the username). Dust asycnhonously returns me the redered text, which is HTML, and then I merrily replace the entire current document with it.

Here is the magic in index.dust that handles that:

{?user}
    <div>Howdy {user.username}</div>
    <button type="button" class="btn btn-large btn-primary" id="logout">Logout</button>
{:else} 
    {>login type="login"/}
    <button type="button" id="lB" class="btn btn-large btn-primary" data-toggle="modal" data-target="#loginForm">Login</button>
    {>login type="register"/}
    <button type="button" id="rB" class="btn btn-large btn-primary" data-toggle="modal" data-target="#registerForm">Register</button>
{/user}

This simply says if the 'user' property is defined show them their name and the logout button, otherwise show the login and register buttons.

Note further I include the 'login' template (compiled from login.dust) with a 'login' parameter. Called a 'partial' in the vernacular, this template creates both the 'login' and 'register' modals, since they are pretty much exactly the same it is templated out - that is what templates are for! The id names are different and some text is slightly different but the modals are 95% the same, hence the template.

SO running:

% grunt dustjs

Will compile all templates and create the 'templates.js' file in the right place. This task is run as part of 'grunt test'

I heartily suggest you become good friends with your favorite templating engine and use it extensively!

Static Code Analysis

Beyond the style and syntax-checker that is jshint, baseapp also includes plato for static code analysis. Plato outputs pretty HTML pages for application-level and file-level code statistics. Also especially nice is plato tracks the history of how our files change over time. Here is the config:

    plato: {
        dashr: {
          options : {
            jshint : false
          }
          , files: {
            'public/plato': ['public/javascripts/**/*.js', 'routes/**/*.js', 'spec/**/*.js', 'app.js'],
          }
        }
      }

This tells plato NOT to use jshint (we have already do that ourselves) and where all of our application files are - including our test files. Plato's output is dumped to 'public/plato' so load up the file 'public/plato/index.html' to see static code analysis in all of its glory. From there you can drill down to specific files if you see any red flags. Remember to keep it simple people!

This task is run as part of 'grunt test'

Total Coverage

This task aggragates coverage information from all sources: client-side unit tests, server-side unit tests, and webdriver/integration tests. If you (or your boss) wants to 'total total' number of all coverage from all tests this is it! To the configuration batman:

    total_coverage: {
        options: {
            coverageDir: './build/reports'
            , outputDir: 'public/coverage/total'
        }
    }

Unbeknownst to you we have been careful about putting all 'coverage.json' files (generated by istanbul) in under the single root 'options.coverageDir' for this very reason - to aggregate them all. This command will recursively look through this directory looking for files named 'coverage*.json' and will mash them all together to generate a single mongo report which is put in the 'options.outputDir' directory. Pointing your browser there and loading up 'lcov-report/index.html' will give you the full monty.

Istanbul can also output coverage information using the 'cobertura' format (vs. the 'lcov' format we have been using). This is useful for CI (and other) tools like Jenkins that understand that format. This task also output 'cobertura' format for the total coverage - which is also dumped into the 'options.outputDir' directory.

This task is run as part of 'grunt test'

The Whole Enchilada

So now we can finally see and understand everything that 'grunt test' does - take a look!

grunt.registerTask('test', [
    'jshint', 
    'jasmine',
    'jasmine_node_coverage',
    'dustjs', 
    'webdriver_coverage',
    'total_coverage',
    'plato'
]); 

Run jshint, run client-side unit tests, run server-side unit tests, compile our templates, run integration tests with code coverage, total up all of the coverage, and run plato. Whew that was fun!

No comments:

Post a Comment