11 Nov 2014

Using Bluebird With Angular Protractor

Async control flow

There are few places where you would want to use a promise. Protractor supports Promises in the onPrepare function but the example uses Q.

That example onPrepare written using Bluebird looks like this;

var Promise = require('bluebird');

onPrepare: function(){
  return Promise.delay(2000);
    .then(function(){
      browser.params.password = '12345';
    });
}

A better example is that the onPrepare function can be used to perform some async setup task like creating a fake User in your database to be able to login.

var User = require('./models/User');

onPrepare: function() {
  // returns a Promise
  return User.create({
    username: 'bulkan',
    password': 'igotdis'
  });
}

Test structure

Protractor uses Jasmine 1.3 and has updated it to automatically resolve Promises.

describe('Home page', function(){
  it('should have username input', function(){
    var username = element(by.css('#username'));
    expect(username.getText()).not.toBeNull();
  });
});

expect automatically resolves the Promise so there is no need to do the following

username.getText().then(function(text){
  expect(text).toEqual('bulkan');
});

Here is another example test that will verify that the home page is rendering Post titles. This time we have to chain onto the .then of the Promises.

var Promise = require('bluebird'),
    Posts = require('./models/Posts');

describe('Home Page', function(){
  it('should have a list of posts', function(done){

    browser.get('/');

    var posts = element(by.repeater('post in posts').column('post.title'));

    Promise.cast(posts.map(function(elm){
      return elm.getInnerHtml();
    }))
    .then(function(titles){
      return titles.sort();
    })
    .then(function(titles){
      return Posts.findAll({attributes: 'title', order: 'title'})
        .tap(function(_titles){
          expect(titles).toEqual(_titles);
        })
     })
     .nodeify(done);
  });
});

We need to Promise.cast the posts.map as we call .nodeify which is a bluebird function. nodeify helps simplify tests by not needing to explicitly call done in the last then and in a catch

Jasmine supports asynchronous tests by passing in a callback function to an it, just like in Mocha. In the test above we find elements by the repeater. The template used might look like;

<div ng-repeat="post in posts">
    <h1> {{::post.title}} </h1>
</div>

There might be an easier/simpler way to do this so please do let me know by commenting below.

14 Apr 2014

AngularJS & Popup Windows

Popup windows are extremely annoying hence most modern browsers block them, agreeably so. That being said one use of popup windows is when doing OAuth. Showing the OAuth authorization dialog in a popup window as not to confuse the user.

If there is a better or different way please comment below.

All the code can be found at angular-popup.

Here is how I solved it using a simple express 4 application and the accompanying AngularJS.

The express code is very simple it just creates two routes. The root/index route renders the view to bootstrap the angular application.

The angular app has one default route / with its controller set to PopupCtrl. In the template popup.html using ng-click we call the function bound on the $scope called showPopup. This is the code for PopupCtrl;

Read the inline comments;

popupApp.controller('PopupCtrl', ['$scope', '$window', '$interval', function PopupCtrl($scope, $window, $interval) {
  'use strict';

  // assign the current $scope to $window so that the popup window can access it
  $window.$scope = $scope;


  $scope.showPopup = function showPopup(){
    // center the popup window
    var left = screen.width/2 - 200
        , top = screen.height/2 - 250
        , popup = $window.open('/popup', '', "top=" + top + ",left=" + left + ",width=400,height=500")
        , interval = 1000;

    // create an ever increasing interval to check a certain global value getting assigned in the popup
    var i = $interval(function(){
      interval += 500;
      try {

        // value is the user_id returned from paypal
        if (popup.value){
          $interval.cancel(i);
          popup.close();
        }
      } catch(e){
        console.error(e);
      }
    }, interval);

  }
}]);

We tell the popup to load up the /popup URL which our express app will render the server side jade template.

extends layout

block content
    <h1>I'm a popup</h1>

    script.
        setTimeout(function(){
            window.opener.$scope.says = 'teapot';
            window.value = true;
        }, 2000);

The template above is simple enough. All it does is after two seconds assing to window.value to indicate to the $interval that the popup has done something important. The popup also assigns a value to window.opener.$scope which is the $scope that was assigned in PopupCtrl.

As we have used ng-model in the default routes template a we will see the text teapot appear in the text input.

Hope this makes sense.