Unit testing and integration testing Meteor applications with BDD using Velocity and Jasmine

UPDATE (22/09/2014) For the updated version of this tutorial for Meteor > 0.9.0 go here

This tutorial concerns Meteor applications Please note that this is a first version of the tutorial, so it will contain a lot of my comments in this format, in which I ask community to help me answer to my questions. Also, some grammatical errors may appear, for which I apologise.

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 by admins, that is users with a role “admin”. Admins cannot delete tutorials with active registrations.

For demonstration purposes, we have decided to keep some functionality on client, and some on servers. We will take the BDD way of developing our application. For testing we will use jasmine-unit and jasmine packages. You may ask: “But what’s the difference?”.

  • Jasmine-unit package is focused strictly on unit testing, running in its own context, where most of the functionality has been stubbed, therefore you can fully focus on testing your code. Remember: With unit tests you test only what’s yours and assume that what is not your just works!. Please note that all console.logs of jasmine-unit tests can only be seen in the system terminal.
  • Jasmine package, runs in the context of the application, preserving all functionality. Therefore it can be used for integration testing, such as testing of template rendering, method calls and more. Please note that all console.logs of jasmine appear in the browser’s javascript console.

We will use these packages in the following order:

  1. Develop server functionality for admins with unit tests using (jasmine-unit)
  2. Develop client functionality for students, create view model with unit tests using (jasmine-unit)
  3. Write integration tests for template rendering using (jasmine).
  4. Create the template for students and admins and write unit tests for event functionality (jasmine-unit)

QUESTION: Is really template rendering an integration test? In my opinion it still belong to unit testing, although with access to full application. What would be the good example of an integration test?

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

$ meteor remove autopublish
$ meteor remove insecure
$ meteor add accounts-password

Add Meteorite packages for routing, role management and of course testing. In our application, we use jasmine-unit package for unit testing, jasmine package for the integration testing and selenium-nightwatch for acceptance testing!.
\n

$ mrt add iron-router
$ mrt add roles
$ mrt add jasmine-unit
$ mrt add jasmine
$ mrt add mocha-web-velocity
$ mrt add velocity-html-reporter
$ mrt add spin
$ mrt add bootstrap-3
$ mrt add accounts-ui-bs3-and-blaze

REMARK: We have also added Mocha framework, which we will not use, but for some reason velocity is not picking up jasmine (not jasmine-unit) tests when mocha is not present and it’s stuck on “loading”. It is a possible bug.

Now, we create standard directories in our application:

$ mkdir tests
$ mkdir tests/jasmine
$ mkdir tests/jasmine/client
$ mkdir client
$ mkdir server
$ mkdir lib

Basic configuration is ready, we now proceed with the preparation of server model.

Chapter 2: Server

In this chapter, we focus on delivering the server functionality to 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 triangulation principle of TDD and BDD, with red-green-refactor approach. 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 folder named tutorial-jasmine-unit.js. We have to respect the *-jasmine-unit.js notation, in order for tests to be picked up by the jasmine-unit package. You can change this naming inside smart.json file of the jasmine-unit package.

// file: tests/tutorial-jasmine-unit.js
(function () {
    "use strict";
    describe("Tutorial", function () {
        it("should be created with name and capacity", function () {
            spyOn(Tutorials, "insert").andReturn(1);

            Meteor.methodMap.createTutorial("Tutorial 1", 20);

            expect(Tutorials.insert).toHaveBeenCalledWith({name: "Tutorial 1", capacity: 20});
        });
    });
})();

It’s quite a lot that we have covered with this test. We have assumed the structure of the Tutorial collection having “name” and “capacity”. Also, we have assumed existence of the “createTutorial” method and that inside this method we call insert to the assumed collection Tutorials.

QUESTIONS:
1. Is it correct to test server methods with: Meteor.methodMap.*?
2. Is it possible to decompose this more and not taking such a huge step?

When we run the meteor application with $ mrt command we will get the famous red dot.

Red dot

Test error says Failed: ReferenceError: Tutorials is not defined. It is time to define the collection and start implementing the server functionality:

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

After defining the collection, we are still in red with following error: Failed: TypeError: Object has no method ‘createTutorial’. Simply put, the createTutorial method is yet missing. So let’s create it!

//file: server/tutorials.js
Meteor.methods({
    createTutorial: function() {
    }
})

Still in red (obviously!), 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: server/tutorials.js
Meteor.methods({
    createTutorial: function(name, capacity) {
        Tutorials.insert({name: name, capacity: capacity});
    }
})

Finally in green! Now we can proceed with making this method call secure using role based assess using the Roles package. We write a following test:

it("should not be created by non-admins", function () {
    spyOn(Meteor, "user").andReturn({});
    spyOn(Roles, "userIsInRole").andReturn(false);
    spyOn(Tutorials, "insert");

    expect(Meteor.methodMap.createTutorial).toThrow();
    expect(Tutorials.insert).not.toHaveBeenCalled();
    expect(Roles.userIsInRole).toHaveBeenCalledWith({}, "admin");
});

In this test we spy on several functions. First, we want our method to check for user and his role, returning mocked values, simulating authorisation for admin. This test will fail since we miss the implementation of the functionality:

createTutorial: function(name, capacity) {
    if (!Meteor.user() || !Roles.userIsInRole(Meteor.user(), "admin")) {
        throw new Meteor.Error(403, "Access Denied");
    }

    Tutorials.insert({name: name, capacity: capacity});
}

We are in green!

But oops, this broke our first test! This is the result of not very fortunate design of our first test, which should be decomposed a bit better.

Since this breaks our first test, we adjust it by adding following spies:

it("should be created with name and capacity", function () {
    spyOn(Tutorials, "insert").andReturn(1);
    spyOn(Meteor, "user").andReturn({});
    spyOn(Roles, "userIsI
    ...

For test completion we also add inverse test (we can make it first red by returning false in the mocked ):

it("should be created only by admins", function () {
    spyOn(Meteor, "user").andReturn({});
    spyOn(Roles, "userIsInRole").andReturn(true);
    spyOn(Tutorials, "insert");

    expect(Meteor.methodMap.createTutorial).not.toThrow();
    expect(Tutorials.insert).toHaveBeenCalled();
    expect(Roles.userIsInRole).toHaveBeenCalledWith({}, "admin");
});

We follow the same approach and create tests and implementation for update and delete of Tutorials. Following is a complete listing of tests of the server functionality. Please note the refactoring we have done replacing spy with mock for Meteor.user(). Also we have joined the first two Tutorial creation tests into one, testing tutorial creation and security access.

// file: tests/tutorial-jasmine-unit.js
(function () {
    "use strict";
    describe("Tutorial", function () {
        // mock
        Meteor.user = function() { return {} };

        it("should be created by admins with name and capacity", function () {
            spyOn(Tutorials, "insert").andReturn(1);
            spyOn(Roles, "userIsInRole").andReturn(true);

            Meteor.methodMap.createTutorial("Tutorial 1", 20);

            expect(Tutorials.insert).toHaveBeenCalledWith({name: "Tutorial 1", capacity: 20});
            expect(Roles.userIsInRole).toHaveBeenCalledWith({}, "admin");
        });

        it("should not be created by non-admins", function () {
            spyOn(Roles, "userIsInRole").andReturn(false);
            spyOn(Tutorials, "insert");

            expect(Meteor.methodMap.createTutorial).toThrow();
            expect(Tutorials.insert).not.toHaveBeenCalled();
        });

        it("should be able to update its name and capacity by admins", function () {
            spyOn(Roles, "userIsInRole").andReturn(true);
            spyOn(Tutorials, "update");

            Meteor.methodMap.updateTutorial(1, "Tutorial 1", 20);

            expect(Tutorials.update).toHaveBeenCalledWith(1, {$set: { name: "Tutorial 1", capacity: 20 }});
            expect(Roles.userIsInRole).toHaveBeenCalledWith({}, "admin");
        });

        it("should not be updated by non-admins", function () {
            spyOn(Roles, "userIsInRole").andReturn(false);
            spyOn(Tutorials, "update");

            expect(Meteor.methodMap.createTutorial).toThrow();
            expect(Tutorials.update).not.toHaveBeenCalled();
            expect(Roles.userIsInRole).toHaveBeenCalledWith({}, "admin");
        });

        it("should be possible to delete tutorial by admins", function () {
            spyOn(Roles, "userIsInRole").andReturn(true);
            spyOn(Tutorials, "remove");

            Meteor.methodMap.removeTutorial("1");

            expect(Tutorials.remove).toHaveBeenCalledWith("1");
            expect(Roles.userIsInRole).toHaveBeenCalledWith({}, "admin");
        });

        it("should not be possible to delete tutorial by non-admins", function () {
            spyOn(Roles, "userIsInRole").andReturn(false);
            spyOn(Tutorials, "remove");

            expect(Meteor.methodMap.removeTutorial).toThrow();
            expect(Tutorials.remove).not.toHaveBeenCalled();
        });

    });
})();
//file: server/tutorials.js
Meteor.methods({
    createTutorial: function(name, capacity) {
        if (!Meteor.user() || !Roles.userIsInRole(Meteor.user(), "admin")) {
            throw new Meteor.Error(403, "Access Denied");
        }
        Tutorials.insert({name: name, capacity: capacity});
    },
    updateTutorial: function(id, name, capacity) {
        if (!Meteor.user() || !Roles.userIsInRole(Meteor.user(), "admin")) {
            throw new Meteor.Error(403, "Access Denied");
        }
        Tutorials.update(id, {$set: {name: name, capacity: capacity}});
    },
    removeTutorial: function(id) {
        if (!Meteor.user() || !Roles.userIsInRole(Meteor.user(), "admin")) {
            throw new Meteor.Error(403, "Access Denied");
        }
        Tutorials.remove(id);
    }
});

Looking at the specification we are still missing one more business requirement: Admins cannot delete tutorials with active registrations. So let’s write a new test:

it("should not be possible to delete tutorial with active registrations", function () {
    spyOn(Roles, "userIsInRole").andReturn(true);
    spyOn(Tutorials, "remove");
    spyOn(TutorialRegistrations, "find").andReturn({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/collections.js
TutorialRegistrations = new Meteor.Collection("tutorialRegistrations");
//file: server/tutorials.js
...
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! Looks like we’re done on server, let’s move to client.

Chapter 3: Client model

In this chapter we create a client view model, which will facilitate its rendering in the template, as well as handle communication with the server. This model includes functionality from both client and server. So let’s get right to it and write tests that handle model specification. We follow the same triangulation (ref-green-refactor) approach as in server, so I hope this part does not need much explication.

First we solve the initialisation:

// file: tests/tutorialViewModel-jasmine-unit.js
(function () {
    "use strict";
    describe("TutorialViewModel", function () {
        it("Should have id, name and capacity", function() {
            var model = TutorialViewModel.create("1", "Name", 10);
            expect(model._id).toEqual("1");
            expect(model.name).toEqual("Name");
            expect(model.capacity).toEqual(10);
        });
    })
})();

And here is the model file:

//file: client/models/tutorialViewModel.js
TutorialViewModel = {
    _id: null,
    name: null,
    capacity: 0,
    init: function(id, name, capacity) {
        this._id = id;
        this.name = name;
        this.capacity = capacity;
        return this;
    },
    create: function(id, name, capacity) {
        return Object.create(TutorialViewModel).init(id, name, capacity);
    }
};

Now, we implement saving, so let’s write couple tests for object creation:

// file: tests/tutorialViewModel-jasmine-unit.js
...
it("Should not save when name is not defined", function() {
    var model = TutorialViewModel.create(null, "", 10);
    expect(model.save).toThrow();
});

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

it("Should save when initialised properly", function() {
    var model = TutorialViewModel.create(null, "Name", 10);

    // create spy for alert
    Object.prototype.alert = function() {};
    spyOn(Object.prototype, "alert");

    spyOn(Meteor, "call").andCallFake(function (functionName, name, capacity, callback) {

        callback("Error", null);
        expect(alert).toHaveBeenCalled();

        callback(null, "1");
        expect(model._id).toEqual("1");
    });

    model.save();

    expect(Meteor.call).toHaveBeenCalled();
    expect(Meteor.call.mostRecentCall.args[0]).toEqual("createTutorial");
    expect(Meteor.call.mostRecentCall.args[1]).toEqual("Name");
    expect(Meteor.call.mostRecentCall.args[2]).toEqual(10);
});

Whoa, whoa, whoa! What just has happened? We did a LOT in this test. First, we have added “alert” function into global namespace since in jasmine-unity it is mocked away. Then, we have created a spy for the Meteor.call in which we test both positive and a negative response. In case of negative response , alert is displayed. In case of positive response new _id is assigned to the model. Last expectations check if Meteor.call method has been called with correct arguments. Following is the implementation of the save function, which makes this test green.

//file: client/models/tutorialViewModel.js
...
save: function() {
    if (!this.name) {
        throw "Name empty or not defined!";
    }
    if (!this.capacity) {
        throw "Capacity 0 or not defined";
    }

    var that = this;
    Meteor.call("createTutorial", this.name, this.capacity, function(err, id) {
        console.log(err + ":" + id);
        if (err) {
            alert(err);
        } else {
            that._id = id;
        }
    });
}

It’s time to extend the functionality of the view model with possibility to save updated changes to the server. Decision whether to insert or update depends on whether _id is defined. First, let’s write the test.

// file: tests/tutorialViewModel-jasmine-unit.js
it("Should save updated changes", function() {
    var model = TutorialViewModel.create("1", "Name", 10);

    spyOn(Object.prototype, "alert");
    spyOn(Meteor, "call").andCallFake(function (functionName, id, name, capacity, callback) {
        callback("Error", null);
        expect(alert).toHaveBeenCalled();
    });

    model.save();

    expect(Meteor.call).toHaveBeenCalled();
    expect(Meteor.call.mostRecentCall.args[0]).toEqual("updateTutorial");
    expect(Meteor.call.mostRecentCall.args[1]).toEqual("1");
    expect(Meteor.call.mostRecentCall.args[2]).toEqual("Name");
    expect(Meteor.call.mostRecentCall.args[3]).toEqual(10);
});

Test is in red, since our save method currently only supports saving a new tutorial. Let’s update the implementation.

//file: client/models/tutorialViewModel.js
save: function() {
    if (!this.name) {
        throw "Name empty or not defined!";
    }
    if (!this.capacity) {
        throw "Capacity 0 or not defined";
    }

    var that = this;

    if (this._id) {
        Meteor.call("updateTutorial", this._id, this.name, this.capacity, function(err) {
            if (err) {
                alert(err);
            }
        });
    } else {
        Meteor.call("createTutorial", this.name, this.capacity, function(err, id) {
            if (err) {
                alert(err);
            } else {
                that._id = id;
            }
        });
    }
}

We follow the same approach to implement the functionality for delete operation. Following are tests for the delete function (we triangulate them one by one).

// file: tests/tutorialViewModel-jasmine-unit.js
it("should delete the existing tutorial", function() {
    var model = TutorialViewModel.create("1", "Name", 10);
    spyOn(Object.prototype, "alert");
    spyOn(Meteor, "call").andCallFake(function (functionName, id, callback) {
        callback("Error", null);
        expect(alert).toHaveBeenCalled();
    });

    model.delete();

    expect(Meteor.call).toHaveBeenCalled();
    expect(Meteor.call.mostRecentCall.args[0]).toEqual("removeTutorial");
    expect(Meteor.call.mostRecentCall.args[1]).toEqual("1");
});

it("should not delete a new tutorial", function() {
    var model = TutorialViewModel.create(null, "Name", 10);
    spyOn(Meteor, "call");

    model.delete();

    expect(Meteor.call).not.toHaveBeenCalled();
});

And here is the implementation that makes these tests green.

//file: client/models/tutorialViewModel.js
delete: function() {
    if (!this._id) return;

    Meteor.call("removeTutorial", this._id, function(err) {
        if (err) {
            alert(err);
        }
    });
}

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 view model. Let’s write the unit test.

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

    spyOn(TutorialRegistrations, "find").andReturn({count: function() { return 8; }});
    spyOn(TutorialRegistrations, "insert");

    model.registerStudent(studentId);

    expect(TutorialRegistrations.insert).toHaveBeenCalled();
    expect(TutorialRegistrations.insert.mostRecentCall.args[0]).toEqual({ tutorialId : '1', studentId : '2' });
});

and the implementation

//file: client/models/tutorialViewModel.js
registerStudent: function(studentId) {
        if (TutorialRegistrations.find({tutorialId: this._id}).count() >= this.currentCapacity) {
            throw "Capacity of the tutorial has been reached!";
        }
        TutorialRegistrations.insert({tutorialId: this._id, studentId: studentId}, function (err, id) {
            if (!err) {
                this.currentCapacity += 1;
            }
        });
    }

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. Since we have decided to keep all the tutorial functionality on server, we need to add a new server method. While at it, we add also the method for removing the registration. Following is a list of tests we used during implementation of server functionality

// file: tests/tutorial-jasmine-unit.js
it("should not be possible to register while at maximum capacity", function() {
    spyOn(Tutorials, "find").andReturn({capacity: 1, currentCapacity: 1});
    expect(Meteor.methodMap.registerForTutorial).toThrow();
});

it("should not be possible to register if registration is present", function() {
    spyOn(Tutorials, "find").andReturn({capacity: 2, currentCapacity: 1});
    spyOn(TutorialRegistrations, "findOne").andReturn({});

    expect(Meteor.methodMap.registerForTutorial).toThrow();
});

it("should be possible to register if within capacity and first registration", function() {
    spyOn(Meteor, "userId").andReturn("2");
    spyOn(Tutorials, "findOne").andReturn({capacity: 2, currentCapacity: 1});
    spyOn(TutorialRegistrations, "findOne").andReturn(null);
    spyOn(TutorialRegistrations, "insert");
    spyOn(Tutorials, "update");

    Meteor.methodMap.registerForTutorial("1");

    expect(TutorialRegistrations.insert).toHaveBeenCalledWith({tutorialId: "1", userId: "2"});
    expect(Tutorials.update).toHaveBeenCalledWith("1", {$set: { currentCapacity: 2}});

});

it("should not be possible to de-register if registration not present", function() {
    spyOn(TutorialRegistrations, "findOne").andReturn();
    expect(Meteor.methodMap.removeRegistration).toThrow();
});

it("should be possible to de-register if registration exists", function() {
    spyOn(Meteor, "userId").andReturn("2");
    spyOn(TutorialRegistrations, "findOne").andReturn({});
    spyOn(TutorialRegistrations, "remove");
    spyOn(Tutorials, "findOne").andReturn({capacity: 2, currentCapacity: 1});
    spyOn(Tutorials, "update");

    Meteor.methodMap.removeRegistration("1");

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

and following is the implementation (make sure you are editing correct files!)

//file: server/tutorials.js
registerForTutorial: function(tutorialId) {
    var userId = Meteor.userId();
    var tutorial= Tutorials.findOne(tutorialId);
    var tutorialRegistration = TutorialRegistrations.findOne({tutorialId: tutorialId, userId: userId});

    if (tutorial.currentCapacity >= tutorial.capacity) {
        throw new Meteor.Error(406, "Tutorial at full capacity");
    }

    if (tutorialRegistration != null) {
        throw new Meteor.Error(406, "Student already registered");
    }

    Tutorials.update(tutorialId, { $set: { currentCapacity: tutorial.currentCapacity + 1}});
    TutorialRegistrations.insert({tutorialId: tutorialId, userId: userId});
},
removeRegistration: function(tutorialId) {
    var userId = Meteor.userId();
    var tutorial= Tutorials.findOne(tutorialId);
    var tutorialRegistration = TutorialRegistrations.findOne({tutorialId: tutorialId, userId: userId});

    if (tutorialRegistration == null) {
        throw new Meteor.Error(406, "Student not registered for this tutorial");
    }

    TutorialRegistrations.remove({tutorialId: tutorialId, userId: userId});
    Tutorials.update(tutorialId, { $set: { currentCapacity: tutorial.currentCapacity - 1 }});
}

With server functionality in place, we can proceed with the view model. For simplicity we remove tests calling alerts.

it("should register student for the tutorial", function() {
    var model = TutorialViewModel.create("1", "Name", 10);
    spyOn(Meteor, "call");

    model.registerStudent();

    expect(Meteor.call).toHaveBeenCalled();
    expect(Meteor.call.mostRecentCall.args[0]).toEqual("registerForTutorial");
    expect(Meteor.call.mostRecentCall.args[1]).toEqual("1");
});

it("should de-register student from the tutorial", function() {
    var model = TutorialViewModel.create("1", "Name", 10);
    spyOn(Meteor, "call");

    model.removeRegistration();

    expect(Meteor.call).toHaveBeenCalled();
    expect(Meteor.call.mostRecentCall.args[0]).toEqual("removeRegistration");
    expect(Meteor.call.mostRecentCall.args[1]).toEqual("1");
});

This is the implementation of the view model:

registerStudent: function() {
    Meteor.call("registerForTutorial", this._id, function(err) {
        if (err) {
            alert(err);
        }
    });
},
removeRegistration: function() {
    Meteor.call("removeRegistration", this._id, function(err) {
        if (err) {
            alert(err);
        }
    });
}

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/TestingMeteor))
...

QUESTION: How to properly test if routing works? Is this part of integration or acceptance testing?

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 integration test that we will write.

QUESTION: I present two different versions of the test. First use is using mocked security checkers. Can this still be considered an integration test? In this test we show how to mock the Handlebars helpers isInRole and currentUser. Second version uses no mocks.

Also please note (what REALLY SUCKS!) that jasmine and jasmine-unit use different notation 😦 Looks like jasmine-unit is still stuck in version 1.3 while jasmine uses modern 2.0 notation. Jasmine-unit looks quite solid with velocity, while jasmine has its issues and sometimes refuses to load (see the trick with mocha-web).

Following is the spec file with mocks for the testing of the header template.

//file:/tests/jasmine/client/header-template-spec.js
// this version contains MOCKS
describe("Header template", function() {
    it("should not show tutorial link to anonymous user", function () {
        spyOn(UI._globalHelpers, "currentUser").and.returnValue(false);

        var div = document.createElement("DIV");
        var comp = UI.render(Template.header);

        UI.insert(comp, div);

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

    it("should show tutorial link to registered user", function () {
        spyOn(UI._globalHelpers, "currentUser").and.returnValue(true);

        var div = document.createElement("DIV");
        var comp = UI.render(Template.header);

        UI.insert(comp, div);

        console.log(div.innerHTML);

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

    it("should show admin link to admins user", function () {
        spyOn(UI._globalHelpers, "isInRole").and.returnValue(true);

        var div = document.createElement("DIV");
        var comp = UI.render(Template.header);

        UI.insert(comp, div);

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

    it("should not show admin link to non-admins", function () {
        spyOn(UI._globalHelpers, "isInRole").and.returnValue(false);

        var div = document.createElement("DIV");
        var comp = UI.render(Template.header);

        UI.insert(comp, div);

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

Following is the spec file without mocks for the testing of the header template. Which one is more correct?. Please note how we login and logout depending on our needs, using asynchronous method calls for login and logout.

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

        UI.insert(comp, div);

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

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

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

        UI.insert(comp, div);

        console.log(div.innerHTML);

        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@example.com', 'apple1', function (err) {
            expect(err).toBeUndefined();
            done();
        });
    });

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

        UI.insert(comp, 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");
        var comp = UI.render(Template.header);

        UI.insert(comp, 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 UI.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 UI.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 = UI.renderWithData(Template.tutorials, data);

        UI.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:

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

    UI.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/tutorials-template-spec.js
it("should show create, modify and delete button to admin user", function () {
    spyOn(UI._globalHelpers, "isInRole").and.returnValue(true);

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

    var comp = UI.renderWithData(Template.tutorials, data);
    UI.insert(comp, 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(UI._globalHelpers, "isInRole").and.returnValue(false);

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

    var comp = UI.renderWithData(Template.tutorials, data);
    UI.insert(comp, 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 unit test of this helper. For unit tests, we use jasmine-unit.

QUESTION: Do you think that this is good practice? I am quite not sure if my template testing is even “integration” testing.

// file: tests/tutorials-template-jasmine-unit.js
(function () {
    "use strict";
    describe("TutorialTemplate", function () {
        it("function canDelete should return true only when tutorial has no registrations", function () {
            // stub values called with accessor "this.currentCapacity"
            Template.tutorials.currentCapacity = 0;
            expect(Template.tutorials.canDelete()).toBeTruthy();
            // cleanup
            delete Template.tutorials.currentCapacity;
        });
        it("function canDelete should return false when there are registrations", function () {
            // stub values called with accessor "this.currentCapacity"
            Template.tutorials.currentCapacity = 1;
            expect(Template.tutorials.canDelete()).toBeFalsy();
            // cleanup
            delete Template.tutorials.currentCapacity;
        });
    });
})();

These test provide a very good example of how we can stub data for function calls and then simply cleanup those data in order for them not to interfere with tests. The biggest challenge her is to find out where the “this” context is currently set to. In helper methods it is always set to the context of the template. It is time to implement our simple helper method to make this test green.

Template.tutorials.helpers({
    canDelete: function() {
        console.log(this);
        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:

//file:/tests/jasmine/client/tutorials-template-spec.js
it("should not show delete button for tutorials with active registrations", function () {
    spyOn(UI._globalHelpers, "isInRole").and.returnValue(true);

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

    var comp = UI.renderWithData(Template.tutorials, data);
    UI.insert(comp, div);

    expect($(div).find(".removeTutorial").length).toEqual(1);
});

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(UI._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:

// file: tests/tutorials-template-jasmine-unit.js
it("should be able to register for tutorial by clicking on the '.registerForTutorial' button", function () {
    Object.prototype.registerStudent = function() {};
    spyOn(Object.prototype, "registerStudent");
    Template.tutorials.fireEvent("click .registerForTutorial", {preventDefault: function() {}});
    expect(Object.prototype.registerStudent).toHaveBeenCalled();
    delete Object.prototype.registerStudent;
});

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}}
            ...

QUESTION: Does this look like an overkill to you? I really like to have my view model with all the functionality inside, since it very much simplifies my testing workflow.

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

Chapter 5: Acceptance Tests with Selenium-Nightwatch

I have decided to make this tutorial “complete” by adding information on how to add and run acceptance tests with Selenium-Nightwatch.

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 have checked out the documentation at the Github page of the Selenium-Nightwatch project and installed the package according to following instructions from the author. This is where my problems begun, since author stressed that it is requited to run meteor with administrator privileges. This completely messed up build of all my packages and from this moment on I had to run all my meteor projects with administrator privileges and 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). 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 install 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 following file

# file: packages/selenium-nightwatch/launch_nightwatch_from_velocity.sh
#!/bin//bash
echo "installing nightwatch 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

Now, you are ready to run your acceptance tests. Running tests with for display in 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.

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 will update this tutorial once I will be sure that it works.

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. In this test, we:

  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 this tutorial was created in “tutorials” page

Later we extend this test, but for now it will suffice. Following is our very first selenium-nightwatch test.

// file: tests/nightwatch/admin-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 test!

To be continued …

Chapter 6: Summary

In this tutorial we have covered quite a lot of grounds in developing Meteor applications following the BDD with Jasmine and Velocity. We have been using two testing packages, jasmine-unit for unit testing and jasmine for integration testing. Jasmine-unit is incredibly fast and reliable package and works very well with velocity. Yet it uses old jasmine syntax for writing tests. Jasmine on the other hand, uses the new 2.0 syntax, yet it is very unreliable with Velocity since the mirror server where tests are executed keep crashing quite often and to obtain test result we often have to reload our application.

Since this is a very first version of this tutorial, I really would welcome any comments and suggestions that would help to make this tutorial better, possibly drawing even more examples. Since I am actively developing a medium scale application for students of my university, I will be updating this tutorial with more examples.

How to create a 3D sound of a large area (e.g. river) in Unity 3D

In our project, we have created a landscape with a large river passing through. The problem arrived, when we tried to add sound effect of running water which would trigger close to the water. Adding several sound effect zones seems highly inefficient and leads to problems when several sound zones overlap.

Instead we call upon help from scripting and some basic linear algebra. The idea is very simple: We will have only one sound source, which will move along a pre-defined linear path defined with a set of waypoints and at any given time it will move to the location closest to the player. We summarise this approach in a set of steps:

  1. Create a set of waypoints along the river bed. Here we have used the Simple Waypoint System
  2. Create an empty GameObject and attach AudioSource with the 3D sound to it.
  3. Attach SoundFollow behaviour, listed below
  4. Done

Following is a script that will follow the path made of waypoints.

using UnityEngine;
using System.Collections;
using SWS;

public class SoundFollow : MonoBehaviour {

    // path manager from the "Simple Waypoints" (http://www.rebound-games.com/?page_id=39)
    // you can assign waypoints directly in waypoints
    public PathManager manager;

    // this can be made public and assigned directly
    private Vector3[] waypoints;

    private Transform player;
    private Transform trans;

    void Awake()
    {
        // specific functionality
        waypoints = new Vector3[manager.waypoints.Length];
        for (var i=0; i<manager.waypoints.Length; i++) {
            waypoints[i] = manager.waypoints[i].position;
        }

        player = GameObject.FindGameObjectWithTag ("Player").transform;
        trans = transform;
    }

    // Update is called once per frame
    void Update () {

        // sort waypoints by distance
        System.Array.Sort<Vector3> (waypoints, delegate(Vector3 way1, Vector3 way2) {
            return Vector3.Distance(way1, player.position).CompareTo (Vector3.Distance(way2, player.position));
        });

        // get the two closest waypoints and find a point in between them
        trans.position = Vector3.Lerp(trans.position, ClosestPointOnLine (waypoints [0], waypoints [1], player.position), Time.deltaTime * 2);
    }

    // thanks to: http://forum.unity3d.com/threads/math-problem.8114/#post-59715
    Vector3 ClosestPointOnLine(Vector3 vA, Vector3 vB, Vector3 vPoint)
    {
        var vVector1 = vPoint - vA;
        var vVector2 = (vB - vA).normalized;

        var d = Vector3.Distance(vA, vB);
        var t = Vector3.Dot(vVector2, vVector1);

        if (t <= 0)
            return vA;

        if (t >= d)
            return vB;

        var vVector3 = vVector2 * t;

        var vClosestPoint = vA + vVector3;

        return vClosestPoint;
    }
}

You can easily drop the “Simple Waypoint System” and manually specify the set of waypoints.

Video

How to create low-poly hair for Unity 3D using Blender 3D and Hair Factory (tutorial)

In this tutorial I want to show you how to create low poly hair for Unity 3D using Hair Factory. Below you’ll find a link to the Hair Factory project as well as the code for the Transparent, Double Sided Unity Shader.

Shader "Transparent/Double-Sided Vertex Lit" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
    }

    SubShader {
        Tags {"RenderType"="Transparent" "Queue"="Transparent"}
        // Render into depth buffer only
        Pass {
            ZWrite On
            Blend SrcAlpha OneMinusSrcAlpha
            ColorMask RGB
            Cull off
            Material {
                Diffuse [_Color]
                Ambient [_Color]
            }
            Lighting On
            SetTexture [_MainTex] {
                Combine texture * primary DOUBLE, texture * primary
            }
        } 
    }
}

IMPORTANT: If you’ll find ant improvement in the shader or any faults in the video, please let me know.

How to install Gitolite and Gitoweb on Ubuntu 14.x

Gitolite and Gitweb allow anyone to have its own private repository with ssh access and configuration via simple text files and git commits. Today I have set on journey of installing it on our production server. Configuring gitolite was a breeze, following the manual specified below, but making Gitweb work was major pain, because of my lacking skills as Linux admin. Here is a short manual to save time to everyone else.

  1. Follow this fantastic tutorial to setup git and gitweb. This tutorial leaves you with everything installed correctly, excpet for the configuration of Apache.

  2. If you are working on a clean install of Ubuntu, it is very probable that CGI is not enabled on your server, and only fcgid is enabled. Enable CGI typing:

    sudo ln -s /etc/apache2/mods-available/cgid.load /etc/apache2/mods-enabled/
    sudo ln -s /etc/apache2/mods-available/cgid.conf /etc/apache2/mods-enabled/
    
  3. What remains is to configure the Apache server modifying the file /etc/apache2/sites-enabled/000-default.conf:
    </VirtualHost>
      ServerAdmin webmaster@localhost
      DocumentRoot /var/www/
    
      ScriptAlias /cgi-bin/ "/usr/share/gitweb/"
      Alias /cgi-bin/static "/usr/share/gitweb/static"
    
      <Directory "/usr/share/gitweb">
        Options Indexes FollowSymlinks ExecCGI
        AllowOverride None
        Order allow,deny
        Allow from all
      </Directory>
    
      ErrorLog ${APACHE_LOG_DIR}/error.log
      CustomLog ${APACHE_LOG_DIR}/access.log combined
    </VirtualHost>
    
  4. Restart Apache using sudo service apache2 restart

  5. That’s it! Now your application is available at http://localhost/cgi-bin/index.cgi

  6. I was having problems with css and javascript not loaded properly since Gitoweb was trying to access the files on non-existing url. While it should normally work, in case you have a similar problem, you have to modify the /usr/share/gitweb/gitweb.cgi file as following (we are adding backslashes):

    # URI of stylesheets
    our @stylesheets = ("/static/gitweb.css");
    # URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
    our $stylesheet = undef;
    # URI of GIT logo (72x27 size)
    our $logo = "/static/git-logo.png";
    # URI of GIT favicon, assumed to be image/png type
    our $favicon = "/static/git-favicon.png";
    # URI of gitweb.js (JavaScript code for gitweb)
    our $javascript = "/static/gitweb.js";