Sunday, December 11, 2011

New Jasmine Realities with Backbone.js and Require.js

‹prev | My Chain | next›

Today, I hope to resolve the last of my jasmine spec failures in my Backbone.js application resulting from the switch to require.js. The switch did not require too much change to get the bulk of my specs passing. I suspect the singleton view (a.k.a. instantiated view) that I am using as the most likely culprit.

The six failures that remain all revolve around singleton views attached to dialogs:


More evidence that it is singleton related is that the individual specs all pass when run individually:


(well, all but one pass individually, but more on that in a bit)

It does not take long to track this down to the afterEach() block in my specs:
  afterEach(function() {
    $('#calendar-add-appointment').dialog('close');
    $('#calendar-add-appointment').remove();

    $('#calendar-edit-appointment').dialog('close');
    $('#calendar-edit-appointment').remove();
  });
Back when I had my entire Backbone application inside a function constructor, the singleton views were redefined each time the class was instantiated. In that sense, they were not true singletons—just singletons within the class. Now they are only being defined when then are required, which only happens on page load.

The solution is mercifully simple—I only need to eliminate the remove() of the singletons after each spec run:
  afterEach(function() {
    $('#calendar-add-appointment').dialog('close');
    $('#calendar-edit-appointment').dialog('close');
  });
With that I am down to a single failure:

That undefined error comes from the create() method of the AppointmentAdd singleton view. In there, I reference appointment_collection:
define(function(require) {
  var $ = require('jquery')
    , Backbone = require('backbone');

  return new (Backbone.View.extend({
    // ...
    create: function() {
      appointment_collection.create({
        title: this.el.find('input.title').val(),
        description: this.el.find('input.description').val(),
        startDate: this.el.find('.startDate').html()
      });
    },
    // ...
  }));
});
That variable is not defined anywhere else in the view. In fact, it is not defined anywhere else in my application. It used to be a class variable pre-require.js, but it is no longer defined anywhere.

Instead of relying on that application class variable, I inject it into the AppointmentAdd dialog when it is reset():
define(function(require) {
  var Backbone = require('backbone')
    , _ = require('underscore')
    , to_iso8601 = require('calendar/helpers/to_iso8601')
    , AppointmentAdd = require('calendar/views/AppointmentAdd');

  return Backbone.View.extend({
    // ...
    events : {
      'click': 'addClick'
    },
    addClick: function(e) {
      console.log("addClick");

      AppointmentAdd.reset({
        startDate: this.el.id,
        collection: this.collection
      });
    }
  });
});
Now, instead of referencing appointment_collection, I can reference this.collection:
define(function(require) {
  var $ = require('jquery')
    , Backbone = require('backbone');

  return new (Backbone.View.extend({
    // ...
    create: function() {
      this.collection.create({
        title: this.el.find('input.title').val(),
        description: this.el.find('input.description').val(),
        startDate: this.el.find('.startDate').html()
      });
    },
    // ...
  }));
});
With that, I have all my tests passing again:

That is a bit of a pain when compared to the convenience of the application class global variable. I have to inject the collection all the way down from the CalendarMonth view through to the Week view and on into the the Day view. Even so, that feels better than tying my AppointmentAdd dialog class to a variable defined in a parent class.

All in all, the switch to require.js did not cause too many jasmine problems. Most of the adjustment came last night, when I had to rework the initialization to be require.js friendly. Tonight, I simply had to adjust to new realities. And fix a bug which makes me grateful I had Jasmine coverage in the first place.

I believe that I am almost done with my require.js exploration. Tomorrow, I will have a quick look at requiring non-Javascript files with require.js and then, who knows?


Day #232

No comments:

Post a Comment