Meteor Boilerplate for Typescript Projects

Meteor Boilerplate for Typescript Projects

Meteor loves Typescript with this new boilerplate!

Quickstart

  1. Clone the meteor boilerplate: git clone git://github.com/tomitrescak/meteor-boilerplate-typescript.git
  2. Run npm install in the main directory (possibly sudo npm install)
  3. Add your files (see section Files).
  4. If you want to use modules use gulp build-debug-modules or gulp build-release-modules gulp task (see section Modules). You can easily run these tasks in your editor (e.g. Webstorm, Sublime)
  5. If you do not want modules use gulp build gulp task.
  6. Run gulp tslint for that extra linting that makes your project shine!
  7. Done!

Modules

To get modules running, you need to register only top level modules in modules:export package. For example, if your modules are MyModule.SubModule1, MyModule.SubModule2.SubSubModule, MyOtherModule.SubModule1 then the top level module is MyModule.
We register this module as following:

//file: /src/packages/exports/exports.js
MyModule = {}
MyOtherModule = {} // another example

and

//file: /src/packages/exports/package.js

...
api.export('MyModule');
api.export('MyOtherModule');
...

Registering module in package assures that the top level module definition is loaded before any other module extension. In the next section, we explain what options we have in building the project.

Building With Gulp

All the magic needed to run the project is hidden inside the gulp files.
Just choose your favourite editor (i.e. Sublime, Webstorm, Visual Studio Code) and run the gulp files to compile your project. These are the tasks that are currently supported:

  • build – Builds the project into /src/js directory, ignoring modules
  • build-debug-modules – Builds the project into /src/js with multiple files, where files are split into three directories (client, server and lib). Files are prefixed by number assuring the correct load order. The load order is detected from references within “*.ts” files.
  • build-release-modules – Builds the project into three files: client/client.js, lib/lib.js and server/server.js.
  • tslint – Runs a tslint

Editors

This boilerplate has been pre-configured for:

  1. Visual Studio Code
  2. WebStorm

Files

Please check out the Typescript-Utils page to see how to write
awesome statically typed template helpers, events and routes.

Packages

We have chosen Semantic UI as the main front end framework.
You can control which components are used in ‘/src/client/lib/semanticui/custom.semantic.json’

  • semantic:ui – Beautifully crafted web pages with Semantic UI
  • useraccounts:semantic-ui – User accounts manipulation in semantic ui
  • accounts-password – Authentification package
  • flemay:autoprefixer – Vital helper for Semantic UI package (can be removed if Semantic UI is removed)
  • iron:router – Popular routing solution
  • multiply:iron-router-progress – Visual progress display (progress colors are defined in /client/stylesheets/progress.css)
  • meteorhacks:subs-manager – Subscription manager to save traffic
  • meteorhacks:fast-render – Blazing fast page load, no more waiting
  • alanning:roles – Roles management
  • dataflows:typescript-utils – Typescript goodies

We leave the rest of the packages up to you, not to annoy you too much.

Karma

This project has been configured to run with Karma (e.g. in Webstorm) for super fast unit testing.
Just click on Run Configurations -> add -> Karma.

You can install karma cli if you plan to run karma tests in terminalsudo npm install -g karma-cli.
You can now simply run karma start.

Structure

  • gulp — Definition of gulp tasks and jobs
  • node_modules
  • src — Your Meteor App
    • js — Generated files
  • typescript — Typescript definition files

License

This project is provided on the MIT license.

Advertisements

Meteor Typescript – Classes and Modules

Typescript is slowly making its name, when only recently Google announced joning forces with Microsoft in creating Typescript 1.5 as the main scripting language for Angular 2.0. Also, Webstorm 10 now supports typescript 1.4 and make Typescript programming a breeze.

Meteor is making it particulary difficult to use typescript as the scripting language due to the following reasons:

  1. The load order of javascript files
  2. Isolation of the code within defined files with var

Imagine following scenario with two classes belonging into the same module, split across two files:

//file:foo.ts
module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}

and

/// <reference path="foo.ts"/>
module Hugo {
 export class Bar extends Foo {
    bar () : string {
      return 'bar1';
    }
  }
}

Load Order

While previous example compiles typescript just fine, you will end up with the following error in the console:

TypeError: Cannot read property 'prototype' of undefined
at __extends (app/models/bar.js:5:21)

I would love to tell you that there exists an automatic method of solving these issues, but till Meteor releases a more sophisticated build mechanism, we are either depending on some dirty hacks, or as I prefer to do this, is to check the current load order and adjust my application accordingly. If you do not mind to do this, you are down to three options.

Option 1: Flat Directory Structure

With the flat directory structure, you depend on alphabetical ordering of loaded files, and therefore you rename foo.ts to _foo.ts so that it will load before bar.ts.

Option 2: Hierarchical Directories

Meteor (for some reason) loads first the child directories, so you can place the foo.ts into a sub-directory, e.g. /dependencies/foo.ts.

Option 3: Libs

Since meteor loads lib directories first, you can place foo.ts into the lib directory and your problem is solved.

All three options suffer from major problem, when in complex projects it becomes very difficult to maintain.

Modules

I have spent significant amount of time trying to figure out the best way of dealing with modules and classes across multiple files. The problem lies with Typescript compiler automatically emitting the var ModuleName making the module private to the given script file. Following is the compiled code for the Foo class:

var Hugo;
(function (Hugo) {
    var Foo = (function () {
        function Foo() {
        }
        Foo.prototype.fooMe = function () {
            return 'fooMe1';
        };
        return Foo;
    })();
    Hugo.Foo = Foo;
})(Hugo || (Hugo = {}));

Finding inspiration on StackOverflow we can easily deal with exporting the module to the global namespace by using this.Hugo = Hugo leading to the following source:

//file:foo.ts
module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}
this.Hugo = Hugo;

Yet, we still keep receiving the Cannot read property 'prototype' of undefined error. The reason for this is, that var Hugo is also emitted into the compiled Bar.js file, ignoring the value in this.Hugo and using empty object {} in the constructor.

var Hugo; // ignores this.Hugo
(function (Hugo) {
    var Bar = (function (_super) {
        __extends(Bar, _super);
        function Bar() {
            _super.apply(this, arguments);
        }
        Bar.prototype.bar = function () {
            return 'bar1';
        };
        return Bar;
    })(Hugo.Foo);
    Hugo.Bar = Bar; // Hugo here is {}
})(Hugo || (Hugo = {}));

Solution

I have checked several options of how to deal with this problem, among which was creating external modules with commonjs and importing them with mrt:exports package, but that solution was not producing any good results. Therefore, I decided to use the evil eval command and redefine the module variable before it gets redefined again. Here is the code:

eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module 

module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}
/// <reference path="lib/foo.ts"/>
eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module 

module Hugo {
 export class Bar extends Foo {
    bar () : string {
      return 'bar1';
    }
  }
}

While neither of these solutions are particularly pretty, they allow to confortably use the Typescript within Meteor, and once Meteor releases more sophisticated support for load ordering anf modules, it can be easily replaced.

If you have any better solution please share it with me.

Growl with Semantic UI

I have modified a jQuery plugin bootstrap growl jquery plugin to be used with Semantic UI.

Extra option is:

  • header: String – renders the header of the growl message

Forked from https://github.com/TimHeckel/meteor-bootstrap-growl

How to use?

Simply install with meteor add tomi:semantic-ui-growl

Example use:

$.semanticUiGrowl('My message', {
    header: 'My Header'
  });

Default Options

$.semanticUiGrowl.defaultOptions = {
  ele: 'body',
  type: 'info',
  offset: {
    from: 'top',
    amount: 20
  },
  align: 'right',
  width: 250,
  delay: 4000,
  allow_dismiss: true,
  stackup_spacing: 10
};

Written with StackEdit.

Meteor Upload File with jQuery File Upload

[EDIT 18/2/2014] Full Cordova support straight out of the box! Follow the guidelines at the project Github page

[EDIT 20/12/2014]: Due to big amount of requests I have added the drag and drop support for file uploads. Currently upload of drag and dropped files cannot be canceled

[EDIT 10/11/2014]: For the most updated documentation to this upload control please go to the project Github page.

In this post I’ll introduce two new packages using which you can easily setup a file upload service to your Meteor server. This solution has following perks:

  1. Uses the famous jQuery file upload system from blueimp.
    1. As a result you can upload any file size (default limit 11GB)
    2. Displays upload progress
    3. Uploads can be canceled
    4. You can upload multiple files
    5. Images can be resized to various sizes using the imagemagick
  2. Saves and serves file from arbitrary folder on your server. This solves problems with hot-code reload, when files are saved into Meteor’s public directory
    1. Possibility to save files to subfolders
    2. Possibility to rename files on the server
  3. Simple configuration and installation! No need to install 10+ packages like with Collection-FS solution

Here is a screenshot of the interface:

Screenshot

Please note that since we are using blueimp’s juery solution, this solution has a full potential of performing client image resizes, chunked uploads, upload resume and more. These features will be gradually added. Pull requests are welcome!

Demo application can be found on Github

Quick Start

Install packages

$ meteor add tomi:upload-server
$ meteor add tomi:upload-jquery

Create directory in the application root

mkdir -p .uploads/tmp

Configure server

//file:/server/init.js
Meteor.startup(function () {
  UploadServer.init({
    tmpDir: process.env.PWD + '/.uploads/tmp',
    uploadDir: process.env.PWD + '/.uploads/'
  })
});

Use template

<template name="yourTemplate">
    {{> upload_bootstrap }}
</template>

DONE!

Installation

The installation is very simple and consists of installing two packages:

$ meteor add tomi:upload-server
$ meteor add tomi:upload-jquery

I have separated these two packages, because often you will want to run your upload service as a separate application. There are several options supported by blueimp, such as Java, ASP, Ruby and more or even node.js Express server installed via NPM. If you wish to use self standing server, install only the tomi:upload-jquery package.

Configuration

Server

First, we need to initialise the server and configure upload paths (see below for explanation of the different options):

//file:/server/init.js
Meteor.startup(function () {
  UploadServer.init({
    tmpDir: '/Users/tomi/Documents/Uploads/tmp',
    uploadDir: '/Users/tomi/Documents/Uploads/',
    getDirectory: function(file, formData) {
      return formData.contentType;
    },
    finished: function(file, folder, formFields) {
      console.log('Write to database: ' + folder + '/' + file);
    }
  })
});

Following options are available for UploadServer.init(options):

Field Type Default Description
tmpDir String null Temporary upload directory
uploadDir String null Path to the upload directory
uploadUrl String ‘/upload/’ Upload route
maxPostSize int 11000000000 Maximum post size (11 GB)
minFileSize int 1 Minimum file size
maxFileSize int 10000000000 Maximum file size (10 GB)
acceptFileTypes RegEx /.+/i, Accepted types of files (e.g. prohibit .exe)
imageTypes RegEx Images which can be resized with Imagemagick (e.g. /\.(gif\|jpe?g\|png)$/i)
imageVersions Object {} Defines the sizes of images which will be converted and saved to upload directory. For example {thumbnailBig: {width: 400, height: 300}, thumbnailSmall: {width: 200, height: 100}}
getDirectory function functions which decides the subdirectory in which the file will be saved. In this function is not defined, no sub-directory is created. For example: function(file, formData) { return '/my/sub/directory'; }
getFileName function Renames the file on the server. In no function is specified, file is saved with the original file name. For example: `function(file, formData) { return ‘Saved-‘ + file; }
finished function Callback

Client

On client we have a possibility to use pre-defined templates, existing for semantic-ui and bootstrap. We can use several upload forms on the page. We can distinguish what kind of content we are uploading, by providing the contentType parameter. We can also limit the file type available for the file picker using parameter fileTypes. Following is an example of how you can include the upload form on your page:

<template name="home">
    <h3>PDFs</h3>
    {{> upload_semantic_ui contentType='pdfs' fileTypes='.pdf' }}
    <h3>Images</h3>
    {{> upload_bootstrap contentType='images' fileTypes='.jpg' multiple=true }}
</template>

If you wish to create your own template for uploading, you can use following call in the rendered callback to set up uploading functionality:

Template['your_upload_template'].rendered = function () {
  Uploader.render.call(this);
};

When using this approach, your template MUST contain following:

  • element with class .uploadFilePicker holding the file picker
  • element with class .uploadProgressHolder holding the file picker
  • element with class .uploadProgressBar showing the progress bar
  • element with class .uploadProgressLabel showing the progress label

If you wish to use custom URL for your uploads this can be configured as following:

Uploader.uploadUrl = 'http://yoururl';

Unity Autosave on Background

All current (free) Unity autosave solutions depend on having the autosave editor opened at all times. With the little help from this blogpost, I have put together a solution that using multi-threading automatically saves your work on background every specific time interval. Plugin is loaded automatically with the project, and reload after every code compilation. It saves both scene and all assets.

The plugin is open-source and can be downloaded from GitHub. Below you’ll find some instructions:

Installation

Copy files into your scripts folder, maintining them in the Editor folder

Usage

In Window > AutoSave configure the time interval of the auto save.
Here you can also enable/disable the autosave as well as configure the verbosity of the plugin.

Implementation instructions

Plugin is using the multi-threading possibilities of Unity editor (which are rather limited). Unity in general obliges to perform any project related actions to be executed on the main thread. This blogpost explains, how to achieve this in Unity Editor (not the game). This blogpost explains how to add mutli-threading into your game. We have adjusted the script from the previously mentioned blogpost and execute the Threader fro the static method rather than depending on the Scriptable object what led into memory leak of the editor.

Apart from the multi-threading operations, we needed to save the user options, such as time interval of auto save. First option was to use a ScriptableObject, but it’s behaviour proved to be rather unexpected when reloading it after code complation. Therefore, we are storing the options in PlayerPrefs and expose public properties such as following:

public static bool ShowMessage { 
    get {
        return PlayerPrefs.GetInt(ShowMessagePref) == 1;
    } 
    set {
        if (value && PlayerPrefs.GetInt(ShowMessagePref) == 0 ||
            !value && PlayerPrefs.GetInt(ShowMessagePref) == 1) 
        {
            PlayerPrefs.SetInt(ShowMessagePref, value ? 1 : 0);
            PlayerPrefs.Save();
        }
    }
}

Bullet-proof Meteor applications with Velocity, Unit Testing, Integration Testing and Jasmine

EDIT: 16/10/2014 – Thanks to the community that pointed out some problem, I have updated the tutorial. I have also updated it to reflect changes for the latest version of API.

In my previous post, I have explained how to develop applications using Behaviour Driven Development (BDD) using velocity with jasmine and jasmine-unit packages. Times has since brought many changes and Meteor 0.9.* world is a different place. I have decided to update my tutorial to reflect the latest changes and improve the previous design. In the next version of the tutorial I’ll look into the future creating apps with Meteor 1.0 and ES6 – Harmony.

In this post, I follow the same example as in the last post, yet we improve the application design for the latency compensation, using client database operations with mini-mongo, rather than server calls. (if you do not understand what I mean do not worry and just trust me that what you do is good and efficient). Therefore, in this tutorial we develop a “bullet-proof” Meteor application for the registration of students to their tutorial groups. You can find complete code on https://github.com/tomitrescak/BulletProofMeteor.

I apologise for any grammatical errors which may appear.

Prologue

In our application students can register for their tutorials. Tutorials have a name and a maximum capacity. Students cannot register if tutorials have reached a maximum capacity. Also, students have to be logged in to register for the tutorial. Tutorials are added to the system only by admins, that is users with a role “admin”. Admins cannot delete tutorials with active registrations.

We will take the BDD way of developing our application. For testing we use the jasmine package, which uses the (jasmine 2.0 syntax)[http://jasmine.github.io/2.0/introduction.html].

If you would like to learn more about the process of BDD or TDD (Test driven development, please check out the following sources). I am also adding some sources on pre-release version of books on Meteor testing

Tutorial Outline

  1. Develop the TutorialDataModel for tutorials and create unit and integration tests as they come. This data model is then used with the transform option of the collection, where each individual record from the Tutorial collection is automatically transformed to the TutorialDataModel.
  2. Develop server methods and create appropriate server unit tests.
  3. Develop templates and write integration tests for template rendering.
  4. Develop event handling and write tests for event functionality.

Chapter 1: Preparing Application

We start by creating an empty Meteor application and adding all necessary packages needed to run our application. If you have downloaded our code from GitHub, you can skip this whole chapter.

$ meteor create Tutorials
$ cd Tutorials
$ rm Tutorials*

Configure Meteor app and add standard Meteor packages and packages for system maintenance

$ meteor remove autopublish
$ meteor remove insecure
$ meteor add accounts-password
$ meteor add alanning:roles
$ meteor add iron:router

Now, we add package for unit testing sanjo:jasmine, package for viewing results of our unit tests in browser velocity:html-reporter, package for internationalisation tap:i18n and other well known packages for “pretifying” our application.

$ meteor add sanjo:jasmine
$ meteor add velocity:html-reporter
$ meteor add sacha:spin
$ meteor add ian:bootstrap-3
$ meteor add ian:accounts-ui-bootstrap-3
$ meteor add tap:i18n

Now, we create standard directories in our application:

$ mkdir -p tests/jasmine/server/unit
$ mkdir -p tests/jasmine/client/integration
$ mkdir client
$ mkdir server
$ mkdir lib

Basic configuration is ready, we now proceed with the definition of the tutorial collection and its data model for the data handling: TutorialDataModel.

Chapter 2: Data Model

In this chapter, we deliver the functionality for the data operations in our app. We follow the specification of the functionality of our application. We start with the initial requirement of our model: Admin can create tutorial.

This tutorial follows the red-green-refactor principle of TDD and BDD, meaning:

  1. We write a failing test which turns the test suite “red”.
  2. We make the test pass with the simplest implementation possible, turning the test suite “green”
  3. We refactor the existing code, repairing broken tests and repeat this process.

First, we place a new file inside the tests/server/unit folder named tutorialDataModelSpec.js. The *Spec.js notation is standard to Jasmine tests. Since the data model applies to both client and server we can use server unit tests to test it.

// file: tests/server/unit/tutorialDataModelSpec.js

"use strict";
describe("Tutorial", function () {
    it("should be created with name and capacity", function () {
        spyOn(Tutorials, "insert").and.callFake(function(doc, callback) {
            // simulate async return of id = "1";
            callback(null, "1");
        });

        var tutorial = new Tutorial(null, "Tutorial 1", 20);

        expect(tutorial.name).toBe("Tutorial 1");
        expect(tutorial.capacity).toBe(20);

        tutorial.save();

        // id should be defined
        expect(tutorial.id).toEqual("1");
        expect(Tutorials.insert).toHaveBeenCalledWith({name: "Tutorial 1", capacity: 20}, jasmine.any(Function));
    });
});

It’s quite a lot that we have covered with this test. We assumed the structure of the Tutorials collection having a create and capacity. Also, we have assumed existence of the Tutorial data model having the constructor with parameters name and capacity and a save() function, which should save data to database calling mongo function Tutorials.insert. We check if insert function has been correctly call with assumed parameters and a callback function, which could be any function: jasmine.any(Function). After insert, a newly assigned id should be defined in the tutorial object. To simulate the return of the id we use the spy and its and.callFake function.

When we run the meteor application with $ meteor command we will get the famous red dot (the image is only for demonstration purposes and the stack trace is different from what you see on the screen).

Red dot

Test error says Failed: ReferenceError: Tutorials is not defined. It is time to define this collection.

//file: lib/tutorialCollection.js
Tutorials = new Meteor.Collection("tutorials");

When we save this file, our app reloads automatically, yet we are still in red with the following error: ReferenceError: Tutorial is not defined. Of course! We still miss our data model, so let’s create it. To save time, let’s implement also the public properties of our model. I like to use the getters/setters approach, since it correctly encapsulates object’s public properties. For the details see here.

//file: lib/tutorialCollection.js
Tutorials = new Meteor.Collection("tutorials");

// A Tutorial class that takes a document in its constructor
Tutorial = function (id, name, capacity, owner) {
    this._id = id;
    this._name = name;
    this._capacity = capacity;
    this._owner = owner;
};

Tutorial.prototype = {
    get id() {
        // readonly
        return this._id;
    },
    get owner() {
        // readonly
        return this._owner;
    },
    get name() {
        return this._name;
    },
    set name(value) {
        this._name = value;
    },
    get capacity() {
        return this._capacity;
    },
    set capacity(value) {
        this._capacity = value;
    }
};

Still in red, now we are dealing with the following error Failed TypeError: Object [object Object] has no method ‘save’. Let’s implement it!

//file: lib/tutorialCollection.js
...
Tutorial.prototype = {
    ...
    save: function() { }
};

Well, we are still in red, now with the following error: *Failed: Expected spy insert to have been called with [ { name : ‘Tutorial 1’, capacity : 20 }, ] but it was never called. *. We need to implement the body of the function.

//file: lib/tutorialCollection.js
Tutorial.prototype = {
    ...
    save: function() {
        // remember the context since in callback it is changed
        var that = this;
        var doc = {name: this.name, capacity: this.capacity};

        Tutorials.insert(doc, function(error, result) {
            that._id = result;
        });
    }
};

Finally in green! We have completed our very first Meteor functionality and bullet-proofed it with the unit test!

Please note that we took a different approach for implementing data models that the one suggested by the meteor team. The original approach depends on using the underscore library and package, which extends the original mongo objects with the prototype functions (see below). Since underscore is automatically stubbed, it would be difficult for us to use it in our unit tests with no actual performance gain.

// meteor approach
// A Tutorial class that takes a document in its constructor
Tutorial = function (doc) {
  _.extend(this, doc);
};
_.extend(Tutorial, {
  save: function () {
  }
});

In our specification, we require that tutorials can only be created by admins. Since we are optimising our app for the latency compensation, we create tutorials on the client side. Therefore, we need to secure the Tutorials collection on the server side using the Collection.allow function. To test this functionality, we implement our very first integration test. But why can’t we just do that with unit test, since it is much faster?

The answer is two-fold, First, in unit tests we test only our new functionality and we assume that everything else works. Since to secure our application we only call standard Meteor API, we do not need to test it, yet we need to test if we are correctly calling it in our implementation. Second, in unit tests all Meteor functionality is stubbed, therefore there is not even possible to get down to nuts and bolts. With this in mind, let us write the test.

// file: tests/client/integration/tutorialIntegrationSpec.js

"use strict";
describe("Tutorial", function () {
    it("should be created by admins", function (done) {
        // login to system and wait for callback
        Meteor.loginWithPassword("admin@tutorials.com", "admin3210", function(err) {
            // check if we have correctly logged in the system
            expect(err).toBeUndefined();

            // create a new tutorial
            var tut = new Tutorial();

            // save the tutorial and use callback function to check for existence
            var id = tut.save(function(error, result) {
                expect(error).toBeUndefined();

                // delete created tutorial
                Tutorials.remove(id);

                Meteor.logout(function() {
                    done();
                })
            });
        });
    });
});

Now, that’s one chunky test. Yet, its internals are quite simple. First, we login as admins and check, if the login was correct. Then, we create a new tutorial and try to save it, specifying the callback function (not yet implemented) to check for the save result. Then, we delete the newly created tutorial in order to keep the database clean. When all is done we call the Jasmine 2.0 done() function to announce the completion of the test with asynchronous calls. Refreshing the page in the browser we see, that our integration tests are now in red with Failed: Expected Error: User not found [403] to be undefined. Let us create an account for admin user and student (normal) user.

//file: server/fixtures.js
Meteor.startup(function() {
   if (Meteor.users.find().count() == 0) {
       var users = [
           {name:"Normal User",email:"normal@tutorials.com",roles:[], password: "normal3210"},
           {name:"Admin User",email:"admin@tutorials.com",roles:['admin'], password: "admin3210"}
       ];

       _.each(users, function (user) {
           var id = Accounts.createUser({
               email: user.email,
               password: user.password,
               profile: { name: user.name }
           });

           if (user.roles.length > 0) {
               Roles.addUsersToRoles(id, user.roles);
           }
       });
   };
});

We need to restart our meteor instance in order to create these accounts. Note, that you can call $ meteor reset to clean your database during development. Next, we need to allow database insertion for admins.

//file: lib/tutorialCollection.js

Tutorials.allow({
insert: function (userId, doc) {
// the user must be logged in, and the document must be owned by the user
return (userId && doc.owner === Meteor.userId() && Roles.userIsInRole(userId, "admin"));
}
});

Running the meteor again we see that our tests are still in red, when after longer interval we receive following error Failed: Error: Timeout – Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. The reason for this error is that done() function never gets called, since our Tutorial.save() function does not implement the callback. Also, we add a new property owner to the tutorial record. Let’s implement this (note the addition of owner: Meteor.userId() in doc.

//file: lib/tutorialCollection.js
Tutorial.prototype = {
    ...
    save: function(callback) {
        ...
        var doc = {name: this.name, capacity: this.capacity, owner: Meteor.userId()};
        Tutorials.insert(doc, function(error, result) {
            ...
        });
    }
};

Oh no! We have broken the unit test: Error: Expected spy insert to have been called with [ { name : ‘Tutorial 1’, capacity : 20 }, ] but actual calls were [ { name : ‘Tutorial 1’, capacity : 20, owner : null }, Function ]. A simple fix, adding the owner to the test:

// file: tests/server/unit/tutorialDataModelSpec.js
...
expect(Tutorials.insert).toHaveBeenCalledWith({name: "Tutorial 1", capacity: 20, owner: null}, jasmine.any(Function));

We are in green! To make our application really bullet-proof, we create also inverse test in which we validate that Tutorial insertion fails for non-admin user. First we make this test fail by expecting the error to be undefined expect(error).toBeUndefined(); and then making it green by expecting the access denied code 403 expect(error.error).toBe(403);

// file: tests/client/integration/tutorialIntegrationSpec.js

"use strict";
describe("Tutorial", function () {
    ...
    it("should not be created by non admins", function (done) {
        // login to system and wait for callback
        Meteor.loginWithPassword("normal@tutorials.com", "normal3210", function(err) {
            // check if we have correctly logged in the system
            expect(err).toBeUndefined();

            // create a new tutorial
            var tut = new Tutorial();

            // save the tutorial and use callback function to check for existence
            var id = tut.save(function(error, result) {
                expect(error.error).toBe(403);

                Meteor.logout(function() {
                    done();
                })
            });
        });
    });
});

We follow the same approach and create tests and implementation for update and delete of Tutorials. You can find the complete listing for the unit test, integration test and an implementation of the Tutorial data model functionality.

Looking at the specification we are still missing one more business requirement: Admins cannot delete tutorials with active registrations. Since this functionality requires checking for data on the server, let’s move the tutorial delete method to server. So let’s write a new test for the server method:

it("should not be possible to delete tutorial with active registrations", function () {
        spyOn(Roles, "userIsInRole").and.returnValue(true);
        spyOn(Tutorials, "remove");
        spyOn(TutorialRegistrations, "find").and.returnValue({count: function() { return 2 }});

        try
        {
            Meteor.methodMap.removeTutorial("1");
        }
        catch (ex) {
            expect(ex).toBeDefined();
        }

        expect(Meteor.methodMap.removeTutorial).toThrow();
        expect(TutorialRegistrations.find).toHaveBeenCalledWith({tutorialId: "1"});
        expect(Tutorials.remove).not.toHaveBeenCalled();
    });

We have decided to keep registrations in a separate collection TutorialRegistrations. Therefore we need to add this collection to our application and extend the functionality of removeTutorial function.

//file: lib/tutorialRegistrationsCollection.js
TutorialRegistrations = new Mongo.Collection("tutorialRegistrations");

Now we are ready to implement the new server method

//file: lib/tutorialCollection.js
...
if (Meteor.isServer) {
    Meteor.methods({
        removeTutorial: function(id) {
            if (!Meteor.user() || !Roles.userIsInRole(Meteor.user(), "admin")) {
                throw new Meteor.Error(403, "Access Denied");
            }
            if (TutorialRegistrations.find({tutorialId: id}).count() > 0) {
                throw new Meteor.Error(406, "Tutorial has registrations");
            }
            Tutorials.remove(id);
        }
    });
}

All green! Now, we would like to automatically instantiate this data model whenever we operate on data from the Tutorials collection. Meteor allows us to do that easily, using the transform function, which is a part of the collection definition and can be overridden of find operation. Let’s implement this!

//file: lib/tutorialCollection
Tutorials = new Mongo.Collection("tutorials", {
    transform: function(doc) {
        return new Tutorial(doc._id, doc.name, doc.capacity, doc.currentCapacity, doc.owner);
    }
});

Looks like we are done with basic CRUD operations, let’s extend the functionality of the model with business rules.

// file: tests/server/unit/tutorialDataModelSpec.js
...
it("Should not save when name is not defined", function() {
    var model = new Tutorial(null, "", 10);
    expect(function() { model.save(); }).toThrow();
});

it("Should not save when capacity is not defined", function() {
    var model = new Tutorial(null, "Name", 0);
    expect(function() { model.save(); }).toThrow();
});

And here is the updated data model

//file: lib/tutorialCollection
...
save: function() {
    if (!this.name) {
        throw new Meteor.Error("Name is not defined!")
    }

    if (!this.capacity) {
        throw new Meteor.Error("Capacity has to be defined or bigger than zero!")
    }
    ...
}

Since we have covered all the functionality of Admin, we now proceed to functionality for students. Our first requirement is that students can register for a tutorial which is not over its capacity. We will include all the tutorial related functionality in the data model. Let’s write the unit test.

it("should allow students to register for the tutorial", function() {
    var model = new Tutorial("1", "Name", 10, 5);
    var studentId = "2";

    spyOn(TutorialRegistrations, "insert");

    model.registerStudent(studentId);

    expect(model.currentCapacity).toBe(2);
    expect(TutorialRegistrations.insert).toHaveBeenCalled();
    expect(TutorialRegistrations.insert.calls.mostRecent().args[0]).toEqual({ tutorialId : '1', studentId : '2' });
});

While making this test, we found that this design has flaw in having to publish all registrations to client. We realise that it is a good idea to add a bit of redundant information to the tutorial about the number of registrations it currently has active. When a new registration is added or removed, this number is changed. Her is the the implementation of the data model function.

//file: lib/tutorialCollection.js
registerStudent: function(studentId) {
    if (this.currentCapacity >= this.capacity) {
        throw "Capacity of the tutorial has been reached!";
    }
    var that = this;
    TutorialRegistrations.insert({tutorialId: this._id, studentId: studentId}, function (err, id) {
        if (!err) {
            that._currentCapacity += 1;
        }
    });
}

In the previous tutorial we have kept the functionality on server, but since in this tutorial we aim to take advantage of the latency compensation we will keep the functionality on the client (we secured the operations on the collection level using the allow method). Following is a list of unit tests.

// file: tests/server/unit/tutorialDataModelSpec.js
it("should not be possible to register while at maximum capacity", function() {
    var tutorial = new Tutorial(1, "Name", 5, 5);

    expect(function() { tutorial.registerStudent(1); }).toThrow("Capacity of the tutorial has been reached!");
});

it("should not be possible to register if registration is present", function() {
    spyOn(TutorialRegistrations, "findOne").and.returnValue({});

    var tutorial = new Tutorial(1, "Name", 5, 4);
    expect(function() { tutorial.registerStudent(1); }).toThrow("Student already registered!");
});


it("should not be possible to de-register if registration not present", function() {
    spyOn(TutorialRegistrations, "findOne").and.returnValue();
    var tutorial = new Tutorial(1, "Name", 5, 4);
    expect(function() { tutorial.removeRegistration(1); }).toThrow("Student not registered!");
});

it("should be possible to de-register if registration exists", function() {
    spyOn(TutorialRegistrations, "findOne").and.returnValue({});
    var tutorial = new Tutorial("1", "Name", 5, 4);

    spyOn(TutorialRegistrations, "remove");
    spyOn(Tutorials, "update");

    tutorial.removeRegistration("2");

    expect(TutorialRegistrations.remove).toHaveBeenCalledWith({tutorialId: "1", userId: "2"});
    expect(Tutorials.update).toHaveBeenCalledWith({_id: "1"}, {$inc: {currentCapacity : -1}});
});

and following is the implementation

//file: lib/tutorialCollection
registerStudent: function(studentId) {
    if (this.currentCapacity >= this.capacity) {
        throw "Capacity of the tutorial has been reached!";
    }

    // check for existing registrations
    if (TutorialRegistrations.findOne({studentId: studentId}) != null) {
        throw "Student already registered!";
    }

    var that = this;
    TutorialRegistrations.insert({tutorialId: this._id, studentId: studentId}, function (err, id) {
        if (!err) {
            that._currentCapacity += 1;

            // update database
            Tutorials.update({_id: that.id}, { $inc: { currentCapacity: 1 }});
        }
    });
},
removeRegistration: function(studentId) {
    var tutorialRegistration = TutorialRegistrations.findOne({tutorialId: this.id, userId: studentId});

    if (tutorialRegistration == null) {
        throw "Student not registered!";
    }

    TutorialRegistrations.remove({tutorialId: this.id, userId: studentId});
    Tutorials.update({_id: this.id}, { $inc: { currentCapacity: - 1 }});
}

Looking at the specification of our system, we should be done with the client and server functionality. Let’s start with templates!

Chapter 4: Templates

Testing templates consists of two difference processes. With unit tests we test template behaviours such as template hooks and events. With *integration tests we test template rendering and the behaviour of HTML controls.

First, we setup (iron) router with all the routes (this is done incrementally during the development). We setup three different routes. First, the welcome route, accessible to all users. Then, route for authorised students, where they can register for tutorials. And the last route for admins, where they can administer tutorials. We also add some standard templates for showing the loading process or page not found. In the router configuration, we also add hooks for security.

//file:lib/router.js
Router.configure({
    layoutTemplate: 'layout',
    loadingTemplate: 'loading',
    notFoundTemplate: 'template404',
    waitOn: function() {
        //return Meteor.subscribe('practicals');
    }
});

Router.map(function() {
    this.route('introduction', {
        path: '/'
    });
});

Router.map(function() {
    this.route('tutorials', {
        path: '/tutorials',
        waitOn: function() {
            return [Meteor.subscribe('tutorials'), Meteor.subscribe('registrations')];
        }
    });
});

Router.map(function() {
    this.route('admin', {
        path: '/admin',
        waitOn: function() {
            return Meteor.subscribe('tutorials');
        }
    });
});

Router.onBeforeAction('loading');

// security (check the full file on [GitHub](https://github.com/tomitrescak/BulletProofMeteor))
...

The waitOn parameter of the route allows Meteor application to pause render until all data from subscription are made available on client. Adding the “loading” hook renders the “loading” template. In order for subscriptions to work, we need to add server publications.

//file:server/publications.js
Meteor.publish('tutorials', function() {
    return Tutorials.find();
});

Meteor.publish('registrations', function() {
    return TutorialRegistrations.find({userId: this.userId});
});

Now we are ready to start developing our templates. Following is our layout template, this template requires no testing (stored in /client/views/application/layout.html).

<br />    {{> header}}
    {{> yield}}

Header Template

First template that requires testing is the header template. We need to make sure that link for admins only appears to admins and that link for tutorials appears only to registered users. This is our first template test that we will write.

//file:/tests/jasmine/client/integration/headerTemplateNomocksSpec.js
describe("Header template - No Mocks", function() {
    it("should not show tutorial link to anonymous user", function () {
        var div = document.createElement("DIV");
        Blaze.render(Template.header, div);

        expect($(div).find("#tutorialsLink")[0]).not.toBeDefined();
    });

    it("should be able to login normal user", function (done) {
        Meteor.loginWithPassword('normal@tutorials.com', 'normal3210', function (err) {
            expect(err).toBeUndefined();
            done();
        });
    });

    it("should show tutorial link to registered user", function () {
        var div = document.createElement("DIV");
        Blaze.render(Template.header, div);


        expect($(div).find("#tutorialsLink")[0]).toBeDefined();
    });

    it("should be able to logout", function (done) {
        Meteor.logout(function (err) {
            expect(err).toBeUndefined();
            done();
        });
    });

    it("should be able to login normal user", function (done) {
        Meteor.loginWithPassword('admin@tutorials.com', 'admin3210', function (err) {
            expect(err).toBeUndefined();
            done();
        });
    });

    it("should show admin link to admins user", function () {
        var div = document.createElement("DIV");
        Blaze.render(Template.header, div);

        expect($(div).find("#adminLink")[0]).toBeDefined();
    });

    it("should be able to logout", function (done) {
        Meteor.logout(function (err) {
            expect(err).toBeUndefined();
            done();
        });
    });

    it("should not show admin link to non-admins", function () {
        var div = document.createElement("DIV");
        Blaze.render(Template.header, div);

        expect($(div).find("#adminLink")[0]).not.toBeDefined();
    });
});

And following is the header template itself.

<br /><br /><div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
                <a class="navbar-brand" href="{{pathFor 'introduction'}}">The Great Clara</a>
            </div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
                    {{> loginButtons }} <!-- here -->
                </ul>
<ul class="nav navbar-nav navbar-right">
                    {{#if currentUser}}

<li><a class="brand" href="{{pathFor 'tutorials'}}" id="tutorialsLink">Tutorials</a></li>
                    {{/if}}
                    {{#if isInRole 'admin'}}

<li><a class="brand" href="{{pathFor 'admin'}}" id="adminLink">Administration</a></li>
                    {{/if}}
                </ul>
</div>
            <!--/.nav-collapse -->
        </div>
</div>

Tutorials Template

In tutorials template we list all tutorials or display that there are no tutorials available (please see the use of Blaze.renderWithData). We display tutorial name, current number of registrations and maximum capacity. Available tutorials are painted with white background, while tutorials with full capacity display with red background. We also display following buttons:

  • Register button is displayed on every available tutorial.
  • Modify button is displayed only for admins on every tutorial
  • Delete tutorial is displayed only for admins on every tutorial which has zero registrations
  • Add tutorial link button is displayed for admins on the bottom on the page

First, we handle template rendering and write integration tests in jasmine. The order of the it specs is given by order of implementation given by the previous specification.

In the first test we check that we render tutorial lines according to the data supplied to the template. Please note the use of Blaze.renerWithData.

//file:/tests/jasmine/client/tutorials-template-spec.sj
describe("Tutorials template", function() {
    it("should show a list of tutorials when there are some available", function () {
        var div = document.createElement("DIV");
        var data = {tutorials: [{}, {}]};
        data.tutorials.count = function() { return 2; }

        var comp = Blaze.renderWithData(Template.tutorials, data);

        Blaze.insert(comp, div);

        expect($(div).find(".tutorialLine").length).toEqual(2);
    });
});

In the second test, within the same file, we check that we display the warning if no tutorials currently exists in the system:

it("should show a warning when no tutorials are available", function () {
    var div = document.createElement("DIV");
    var comp = Blaze.renderWithData(Template.tutorials, {tutorials: {count: function() { return 0; }}});

    Blaze.insert(comp, div);

    expect($(div).find("#noTutorialsWarning")[0]).toBeDefined();
});

Whenever you want to see how your rendering is doing, you can log it with console.log(div) and see the result in your console.

Next, we want to have all tutorials sorted by title. Creating test for this is going to be a bit tricky, since we want to assign and sort the data in the router and not in the template helper. Here is our test spec proposal:

//file:/tests/jasmine/client/tutorials-template-spec.js
it ("should sort tutorials by name", function() {
    var route = _.findWhere(Router.routes, {name: "tutorials"});
    spyOn(Tutorials, "find").and.returnValue({});

    // call the function "data()"
    var data = route.options.data();

    expect(Tutorials.find).toHaveBeenCalled();
    expect(Tutorials.find.calls.mostRecent().args[0]).toEqual({});
    expect(Tutorials.find.calls.mostRecent().args[1].sort.name).toEqual(1);
    expect(data).toEqual({tutorials: {}});
});

And following is the modification of the router

file:/lib/route.js
Router.map(function() {
    this.route('tutorials', {
        path: '/tutorials',
        waitOn: function() {
            return [Meteor.subscribe('tutorials'), Meteor.subscribe('registrations')];
        },
        data: function() {
            return {tutorials: Tutorials.find({}, {sort: {name: 1}}) };
        }
    });
});

If we would use a template helper to obtain our data, instead of using iron router’s data() function, creating the test would be easier:

//file:/client/views/tutorials/tutorials.js
Template.tutorials.tutorials = function() {
    return Tutorials.find({}, {sort: {name: 1}}
};

//file:/tests/jasmine/client/tutorials-template-spec.js
it ("should sort tutorials by name", function() {
    spyOn(Tutorials, "find").and.returnValue({});

    // call the function "data()"
    var data = Template.tutorials.tutorials();

    expect(Tutorials.find).toHaveBeenCalled();
    expect(Tutorials.find.calls.mostRecent().args[0]).toEqual({});
    expect(Tutorials.find.calls.mostRecent().args[1].sort.name).toEqual(1);
    expect(data).toEqual({tutorials: {}});
});

This is the template we have so far:

<br /><br /><h1>Tutorials</h1>
    {{#if tutorials.count}}
        {{#each tutorials}}

<div class="tutorialLine">Line</div>
        {{/each}}
    {{else}}

<p class="well bg-warning" id="noTutorialsWarning">There are no tutorials available ...

    {{/if}}

Next, we will implement all admin buttons that allow us to create and modify tutorials. We have decided to create only one test case for all buttons. This test is using stubs for authorisation checks.

//file:/tests/jasmine/client/integration/tutorialsTemplateSpec.js
it("should show create, modify and delete button to admin user", function () {
    spyOn(Blaze._globalHelpers, "isInRole").and.returnValue(true);

    var data = {tutorials: [{}, {}]};
    data.tutorials.count = function() { return 2; };

    Blaze.renderWithData(Template.tutorials, data, div);

    expect($(div).find("#createTutorial")[0]).toBeDefined();
    expect($(div).find(".modifyTutorial").length).toEqual(2);
    expect($(div).find(".removeTutorial").length).toEqual(2);
});

it("should not show create, modify and delete button to non-admin user", function () {
    spyOn(Blaze._globalHelpers, "isInRole").and.returnValue(false);

    var data = {tutorials: [{}, {}]};
    data.tutorials.count = function() { return 2; };

    Blaze.renderWithData(Template.tutorials, data, div);

    expect($(div).find("#createTutorial")[0]).not.toBeDefined();
    expect($(div).find(".modifyTutorial")[0]).not.toBeDefined();
    expect($(div).find(".removeTutorial")[0]).not.toBeDefined();

    console.log(div.innerHTML);
});

And this is the template we have so far:

<h1>Tutorials</h1>
    {{#if tutorials.count}}
        {{#each tutorials}}

<div class="row tutorialLine">
                {{name}} <span class="badge">{{currentCapacity}} / {{capacity}}</span>

                {{#if isInRole 'admin'}}
                    <span class="glyphicon glyphicon-trash"></span> Delete
                    <a class="btn btn-info pull-right modifyTutorial" href="{{pathFor 'admin'}}"><span class="glyphicon glyphicon-edit"></span> Edit</a>
                {{/if}}
            </div>
        {{/each}}
    {{else}}

<p class="well bg-warning" id="noTutorialsWarning">There are no tutorials available ...

    {{/if}}

    {{#if isInRole 'admin'}}

<hr />
        <a href="{{pathFor 'admin'}}" class="btn btn-primary" id="createTutorial">Create Tutorial</a>
    {{/if}}

Looks like our template is ready! Well, not entirely. One of the requests we had, was that admins cannot delete tutorials which have at least one registration. It is a good practice to hide all actions that are not currently possible for clarity. For this we implement a new template helper, starting with the template (integration) test of this helper.

//file:/tests/jasmine/client/integration/tutorialsTemplateSpec.js
it("function canDelete should return true only when tutorial has no registrations", function () {
    expect(Template.tutorials.canDelete.call({currentCapacity: 0})).toBe(true);
});

it("function canDelete should return false when there are registrations", function () {
    expect(Template.tutorials.canDelete.call({currentCapacity: 1})).toBeFalsy();
});

These test provide a very good example of how we can easily use the prototype.call function to call a template function with a specific context. It is time to implement our simple helper method to make this test green.

Template.tutorials.helpers({
    canDelete: function() {
        return this.currentCapacity == 0;
    }
});

We can now use this helper in our tutorials template to hide all delete buttons and prohibit deleting tutorials with active registrations. We write following test, in which we pass data with two tutorials, from which only one should be able to delete:

it("function canRegister should return true when capacity is available and student is not yet registered", function () {
    // stub values called with accessor "this.currentCapacity"
    spyOn(TutorialRegistrations, "find").and.returnValue({count: function() { return 0; }});
    expect(Template.tutorials.canRegister.call({currentCapacity: 1, capacity: 2})).toBeTruthy();
});

it("function canRegister should return false when reached capacity is available and student is not yet registered", function () {
    expect(Template.tutorials.canRegister.call({currentCapacity: 2, capacity: 2})).toBeFalsy();

    spyOn(TutorialRegistrations, "find").and.returnValue({count: function() { return 1; }});
    expect(Template.tutorials.canRegister.call({currentCapacity: 1, capacity: 2})).toBeFalsy();
});

We adjust the template accordingly using the {{#if canDelete}}...{{/if}}. This makes our test green, but breaks another test, in which we test if all admin buttons are displayed. We adjust this test to include information on the capacity of data:

it("should show create, modify and delete button to admin user", function () {
    spyOn(Blaze._globalHelpers, "isInRole").and.returnValue(true);

    var data = {tutorials: [{currentCapacity: 0}, {currentCapacity: 0}]};
...

Following the very same approach we add the button for student registration for tutorials. This button is visible for tutorials which have available capacity and student is not yet registered for them. To achieve this we write a unit test for the helper method and an integration test for testing the button visibility (Since there is nothing new to show, please see the complete code on GitHub).

Phew! So far we have covered a lot, and yet this is still not the end! Currently, we have a working client and server functionality and our templates render what we need. Yet we miss all the page interactivity. Therefore, we need to add and test template events. We contemplate the tutorial registration event, the rest can be found on GitHub. Let’s start with our unit test.

In this test, we cover clicking on the button with class “.registerForTutorial”, which should trigger call of the registerStudent() from our view model. Here it goes:

var data = new Tutorial();

spyOn(data, "registerStudent");
spyOn(Blaze, "getData").and.returnValue(data);

Template.tutorials.__eventMaps[0]["click .registerForTutorial"].call({templateInstance: function() {}}, {preventDefault : function() {}});

expect(data.registerStudent).toHaveBeenCalled();
});

This is a highly hacked version of how to directly call the template event and hopefully we will soon see a simpler method.

We know, that in helper we will be calling the this.registerStudent(). Since in unit test the context of this varies from the real application, the safest way to stup and test this action is by adding it to the Object. Now, to make this test green, we implement it like this:

//file:/client/views/tutorials/tutorials.js
Template.tutorials.events({
    "click .registerForTutorial": function(e) {
        e.preventDefault();
        this.registerStudent();
    }
});

But, this has a major flaw! Context of “this” is within cursor of the Tutorials collection. In our template we browse lines of the collection directly, we do not instantiate our model. We need to adjust this by creating a new helper:

//file:/client/views/tutorials/tutorials.js
Template.tutorials.helpers({
    ...
    tutorialModel: function() {
        return TutorialViewModel.create(this._id, this.name, this.capacity, this.currentCapacity);
    }
});

and adjusting our template

<br /><br /><h1>Tutorials</h1>
    {{#if tutorials.count}}
        {{#each tutorials}}
            {{#with tutorialModel}}
            ...

With this, we have exhausted all the examples we could draw during development of our applications. Please download the full code from GitHub.

Acceptance testing of Meteor Application using Selenium-Nightwatch and Velocity

In my previous tutorial, I presented how to develop Meteor applications in BDD style with unit testing and integration testing. It is time to explore, how to perform acceptance testing of your app. For this, we have selected the selenium-nightwatch package and velocity.

Since this is my first time using this test suite, I plan to learn about it as I write. If you have any comments, please let me know if I am using it incorrectly

I started by reading the documentation at the Github page of the Selenium-Nightwatch project and tried to install the package according to following instructions from the author. This is where my problems begun, since author stressed that it is required to run meteor with administrator privileges, what completely messed up build of all my packages and from this moment on I had to run all my meteor projects with administrator privileges. I shrieked in horror. After several hours of trial and error I messed up my whole Meteor installation and had to reinstall Node.js, Meteor and all Meteorite packages (I deleted them from ~/.meteorite directory).

I really did not want to run Meteor with admin privileges, so I spent quite some time figuring it out. Following is a bash script which will safely bring selenium-nightwatch into your project, without requiring to run meteor as administrator (yet nightwatch still has to be run with administrator privileges). Also, in my approach I install night watch globally, sine the provided scripts try to install night watch into project directory, what many times failed and also required admin privileges to run meteor.

// in terminal
$ cd toYourProjectRoot
$ mrt add selenium-nightwatch
$ sudo -H npm install -g nightwatch
$ ln -s packages/selenium-nightwatch/launch_nightwatch_from_app_root.sh run_velocity_from_app_root.sh
$ ln -s packages/selenium-nightwatch/launch_nightwatch_from_app_root.sh run_root_nightwatch.sh

Now we need to modify the launcher scripts, so that they do not call the local night watch instance, but the global one (I have commented out original functionality and added mine):

# file: packages/selenium-nightwatch/launch_nightwatch_from_app_root.sh
#!/bin//bash
#echo "installing nightwatch in .meteor/local/build"
#  cd .meteor/local/build
#  sudo npm install nightwatch@0.5.3
#  cd ../../../

echo "running nightwatch from app root"
#   sudo .meteor/local/build/node_modules/nightwatch/bin/nightwatch -c packages/selenium-nightwatch/nightwatch_from_app_root.json $1 $2
sudo nightwatch -c packages/selenium-nightwatch/nightwatch_from_app_root.json $1 $2

Also we modify the launcher file for velocity:

# file: packages/selenium-nightwatch/launch_nightwatch_from_velocity.sh
#!/bin//bash
# echo "installing night watch in .meteor/local/build"
#  cd .meteor/local/build
#  sudo npm install nightwatch@0.5.3
#  cd ../../../

echo "velocity is launching nightwatch"
#   sudo .meteor/local/build/node_modules/nightwatch/bin/nightwatch -c packages/selenium-nightwatch/nightwatch_from_velocity.json $1 $2
sudo nightwatch -c packages/selenium-nightwatch/nightwatch_from_velocity.json $1 $2

WARNING! Up to date I was not able to run selenium-nightwatch with Velocity due to the possible bug in parsing the selenium report files. I had to delete all selenium-nightwatch report files to restore the functionality. I will update this tutorial once I will be sure that it works.

We are ready to run your acceptance tests! To run tests for velocity call simply ./run_velocity_nightwatch.sh in your application root.

To run tests without velocity run ./run_root_nightwatch.sh -t path/to/testfile.js. In this tutorial we will be running the test only in terminal, since velocity was crashing our app when parsing selenium report.

We are ready to write our first test! The documentation makes it look dead simple, so let’s get right to it. First, I checked in smart.json file how velocity identifies test suites. In case of selenium-nightwatch package, this information resides in nightwatch_from_velocity.json file and tells us following "src_folders" : ["tests/nightwatch"]. So, we create a directory in /tests/nightwatch and try to code our first acceptance test.

// file: tests/nightwatch/helloworld-test.js
module.exports = {
"Hello World" : function (client) {
client
.url("http://127.0.0.1:3000")
.waitForElementVisible("body", 1000)
.assert.title("Hello World")
.end();
}
};

We run this test in terminal with ./run_root_nightwatch.sh -t tests/nightwatch/admin-test.js and obtain following result:

Terminal

Since in our template we did not set the page title our test fails. Great, we got it working! Let’s try to write something more meaningful related to our application! In this test, we expand the example from our previous tutorial and we perform following checks:

  1. Login as admin in the introduction (home) page
  2. We navigate to “/createTutorial” page
  3. We create a new tutorial
  4. We check that after this tutorial was created, application redirects to “tutorials” page

And here is the code of the test:

//file:tests/nightwatch/admin-test.js
module.exports = {
    "Test admin functionality - create, modify and delete tutorial" : function (client) {
        client
            .url("http://localhost:3000/createTutorial")
            .waitForElementVisible("body", 1000)
            .click("li#login-dropdown-list a")
            .pause(100)
            .assert.visible("input#login-email")
            .assert.visible("input#login-password")
            .setValue("input#login-email", "admin@example.com")
            .setValue("input#login-password", "apple1")
            .click("button#login-buttons-password")
            .assert.visible("input#tutorialName")
            .assert.visible("input#tutorialCapacity")
            .assert.visible("button#modifyTutorialButton")
            .setValue("input#tutorialName", "NightWatchTutorial")
            .setValue("input#tutorialCapacity", "10")
            .click("button#modifyTutorialButton")
            .pause(2000)
            .assert.urlEquals('http://localhost:3000/tutorials')
            .end();
    }
};

We run this test as ./run_root_nightwatch.sh -t tests/nightwatch/admin-test.js and following is the output we obtain in console:

Terminal

Mission accomplished, well, at least part of it! You can download the code from GitHub.