Thursday 15 May 2014

Team Foundation Server, Continuous Integration and separate projects for JavaScript unit tests

Do you like to separate out your unit tests from the project you are testing? I imagine so. My own practice when creating a new project in Visual Studio is to create a separate unit test project alongside whose responsibility is to house unit tests for that new project.

When I check in code for that project I expect the continuous integration build to kick off and, as part of that, the unit tests to be run. When it comes to running .NET tests then Team Foundation Server (and it's cloud counterpart Visual Studio Online) has your back. When it comes to running JavaScript tests then... not so much.

This post will set out:

  1. How to get JavaScript tests to run on TFS / VSO in a continuous integration scenario.
  2. How to achieve this *without* having to include your tests as part of web project.

To do this I will lean heavily (that's fancy language for "rip off entirely") on an excellent blog post by Mathew Aniyan which covers point #1. My contribution is point #2.

Points #1 and #2 in short order

First of all, install Chutzpah on TFS / VSO. You can do this by following Steps 1 - 6 from Mathew Aniyan's post. Instead of following steps 7 and 8 create a new unit test project in your solution.

Edit 29/05/2014: Matthew Manela (creator of Chutzpah) has confirmed that this is the correct approach - thanks chap!

To our unit test project add your JavaScript unit tests. These should be marked in Visual Studio with a Build Action of "Content" and a Copy to Output Directory of "Do not copy". You should be able to run these tests locally using the Visual Studio Chutzpah extension - or indeed in some other JavaScript test runner. Then, and this is the important part, edit the csproj file of your unit test project and add this Import Project statement:


  <Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />

Ordering is important in this case. It matters that this new statement sits after the other Import Project statements. So you should end up with a csproj file that looks in part like this: (comments added by me for clarity)


  <!-- Pre-existing Import Project statements start -->
  <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- Pre-existing Import Project statements end -->

  <!-- New addition start -->
  <Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
  <!-- New addition end -->

Check in your amended csproj and the unit tests to TFS / VSO. You should see the JavaScript unit tests being run as part of the build.

Monday 5 May 2014

TypeScript, JSDoc and Intellisense

Days of Yore

It was my first job. The web was alive and well at this point but still very much in it's infancy. Newspapers had only recently moved on from calling it "the information superhighway". No-one was doing real programming for the web - the desktop was where it was at.

As for me, I was writing call centre software. It was all very exciting. Here was the idea: the phone on your desk would start ringing and through the magic of TAPI our app would be presented with the telephone number of the dialer. It would then look up that telephone number in the appropriate CRM application and pop the callers details on the screen. You'd pick up the phone and bellow "why hello Mr Jones!" and either impress the caller with your incredible fore-knowledge of who had rung you or perhaps terrify them with our Brave New Orwellian World.

My job was to work out how to call into the APIs of the various CRM applications / databases being used and extract the relevant information. So it goes without saying that I have spent a lot of time with badly documented APIs. Or in fact undocumented APIs. I know pain my friend...

Hours and days were spent debugging and walking APIs just to find out what they could do and what information they exposed. This, I need hardly say, was dull and tedious work. Having spent longer than I care to remember with no more information on an API than method names has left its mark on me. I am consequently keener than your average dev on documentation and intellisense. When you've stared at the coalface of the Lotus Notes API for 2 weeks with only Dephi 3 as your constant companion you'd feel the same way too. (This was before the days of Google and actually being able to find stuff on the internet.)

If you can convey information about the API that you're building then I'd say you're duty-bound to do so. Or at least that it's good manners.

Definitely Intellisensed

When I started getting involved with the Definitely Typed project my focus was on giving good Intellisense. Where there was documentation for an API I wanted to get that popping in front of users when they hit the "." key:

As the above screenshot demonstrates TypeScript supports Intellisense through a slightly tweaked implementation of JSDoc:

With 0.8.2, the TypeScript compiler and tools now support JSDoc comments.

In the TypeScript implementation, because types are already part of the system, we allow the JSDoc type annotation to be elided, as in the example above.

You can now document a variety of language constructs (including classes, modules, interfaces, and functions) with comments that become part of the information displayed to the user. We’ve also started extending lib.d.ts, the default JS and DOM API library, with JSDoc comments.

Partly as an exercise in getting better acquainted with TypeScript and partly responding to my instinctive need to have nicely documented APIs I decided to start adding JSDoc comments to the world's most popular typings file jquery.d.ts.

Why jquery.d.ts?

Well a number of reasons:

  1. I used jquery.d.ts already myself and I'm a firm believer in eating your own dogfood
  2. jQuery is well documented. I needed a source of information to power my JSDoc and api.jquery.com had my back.
  3. jquery.d.ts was widely used. Given how ubiquitous jQuery has become this typing file was unsurprisingly the most popular in the world. That was key for me as I wanted feedback - if I was making a mess of the typings I wanted someone to pitch in and tell me.

Just to digress once more, points #2 and #3 turned out to be of particular note.

Concerning point #2, I did find the occasional error or inconsistency in the jQuery API documentation. These were definitely the exception rather than the rule though. And thanks to the very helpful Dave Methvin these actually lead to minor improvements to the jQuery API documentation.

Concerning point #3 I did indeed get feedback. As well as enriching jquery.d.ts with JSDoc goodness I also found myself fixing slight errors in the typings. Here and there I would find examples where jquery.d.ts was out of line the with API documentation. Where this was the case I would amend the typings to bring them into line - trying to make jquery.d.ts entirely API-compliant. This was not always popular. But despite the heat it generated I think it ended up leading to a better typing file. I'm again grateful for Dave Methvin's thoughtful contributions.

Turning API documentation into JSDoc

I wanted to take an example of API documentation and demonstrate how that can be applied to a typing file with particular focus on how JSDoc comments can be created to drive Intellisense. So let's take everyone's favourite jQuery method: val. The documentation of val can be found here: api.jquery.com/val

By the way, check out the *entirely* intuitive URL. Now you've clocked just how straightforward that is you've probably a fair idea how you could find pretty much any jQuery documentation you might need without recourse to Google. Brilliant!

Let's take a look at what val looked like before JSDoc in the first version of the typing available on GitHub. (By the way, remember the original jquery.d.ts came out of the TypeScript team):


    val(): any;
    val(value: string[]): JQuery;
    val(value: string): JQuery;
    val(value: number): JQuery;
    val(func: (index: any, value: any) => any): JQuery;

And now let's look at jquery.d.ts after JSDoc:


    /**
     * Get the current value of the first element in the set of matched elements.
     */
    val(): any;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param value A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.
     */
    val(value: string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param value A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.
     */
    val(value: string[]): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string) => string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string[]) => string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: number) => string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string) => string[]): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string[]) => string[]): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: number) => string[]): JQuery;

Many changes yes? Let's break it down a little.

1. You have 20 seconds to comply (with the API)

The first thing to note is the number setter method:


    val(value: number): JQuery;

Let's have a look at the jQuery documentation for the simple setter:

.val( value )

value
Type: String or Array
A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.

See the problem? There is *no* number setter. The typings are wrong. So let's remedy this:


    val(value: number): JQuery;

2. String and Array of String setters

The documentation states that we have setters which accept String and Array of String. These are already modeled in the existing typings by the string and string[] overloads:


    val(value: string[]): JQuery;
    val(value: string): JQuery;

So let's enrich these typings with some JSDoc:


    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param value A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.
     */
    val(value: string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param value A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.
     */
    val(value: string[]): JQuery;

If you look you can see we've added a related JSDoc style comment block prior to each overload. The first part of the comment ("Set the value of...") is the overarching Intellisense that is displayed. Each of the @param statements represents each of the parameters and it's associated comment. By comparing the API documentation to the JSDoc it's pretty clear how the API has been transformed into useful JSDoc.

It's worth noting that I could have taken the choice to customise the @param value comments based on the overload I was JSDoc-ing. Arguably it would have been more useful to have something like this instead:


    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param value A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.
     */
    val(value: string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param value A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.
     */
    val(value: string[]): JQuery;

After some pondering I decided not to take this approach, just to maintain that close relationship between jquery.d.ts and api.jquery.com. It's open to debate how useful that relationship actually is so I thought I'd just highlight this as a choice I made.

3. Getter

The jQuery documentation for the getter looks like this:

.val() Returns: String or Number or Array

Description: Get the current value of the first element in the set of matched elements.

So the val() overload can return a string, a number or a string[]. Unfortunately there is no real way to model that in TypeScript at present due to the absence of "union types". Union types are being discussed at present but in TypeScript v1.0 world the only viable approach is returning the any type. This implies val() returns any possible JavaScript value from boolean to Function and straight on 'til morning. So clearly this isn't accurate but importantly it also allows for the possibility of val() returning string, number or string[].

The final getter typing with JSDoc applied ends up looking like this:


    /**
     * Get the current value of the first element in the set of matched elements.
     */
    val(): any;

As you can see the "Get the current value..." from the API docs has been used as the overarching Intellisense that is displayed for the getter.

4. The Function setter

Finally we're going to take a look at the Function setter which is documented as follows:

.val( function(index, value) )

function(index, value)
Type: Function()
A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.

If you cast your eyes back to the original typings for the Function setter you'll see they look like this:


    val(func: (index: any, value: any) => any): JQuery;

This is a good start but it's less accurate than it could be in a number of ways:

  1. index is a number - we needn't keep it as an any
  2. value is the old value - we know from our getter that this can be a string, number or string[]. So we can lose the any in favour of overloads which specify different types for value in each.
  3. The return value of the function is the value that should be set. We know from our other setters that the possible types allowed here are string and string[]. (And yes I'm as puzzled as you are that the getter can return a number but the setter can't set one.) That being the case it makes sense for us to have overloads with functions that return both string and string[]

So, we've got a little tidy up to do for #1 and extra overloads to add for #2 and #3. We're going to replace the single Function setter with 3 overloads to cater for #2. Then for #3 we're going to take each of the 3 overloads we've just created and make 2 overloads place of each to handle the different return types. This will lead us with the grand total of 6 overloads to model our Function setter!


    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string) => string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string[]) => string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: number) => string): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string) => string[]): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: string[]) => string[]): JQuery;
    /**
     * Set the value of each element in the set of matched elements.
     *
     * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments.
     */
    val(func: (index: number, value: number) => string[]): JQuery;

A cursory glance shows that each of the overloads above shares the same JSDoc. Each has the "Set the value..." from the API docs as the overarching Intellisense that is displayed for the Function setter. And each has the same @param func comment as well.

It could be you...

This post is much longer than I ever intended it to be. But I wanted to show how easy it is to create typings with JSDoc to drive Intellisense. For no obvious reason people generally don't make a great deal of use of JSDoc when creating typings. Perhaps the creators have no good source of documentation (a common problem). Or perhaps people are not even aware it's a possibility - they don't know about the TypeScript support of JSDoc. In case it's the latter I think this post was worth writing.