Reputation: 279
I'm currently working through the PeepCode video on Backbone.js but attempting to write it all in CoffeeScript rather than bare JavaScript.
So far so good, except when I try to run Jasmine tests on the code I run into some TypeErrors:
TypeError: Cannot call method 'isFirstTrack' of undefined
TypeError: Cannot call method 'get' of undefined
My CoffeeScript/Backbone file looks like this:
jQuery ->
class window.Album extends Backbone.Model
isFirstTrack: (index) ->
index is 0
class window.AlbumView extends Backbone.View
tagName: 'li'
className: 'album'
initialize: ->
@model.bind('change', @render)
@template = _.template $('#album-template').html()
render: =>
renderedContent = @template @model.toJSON()
$(@el).html(renderedContent)
return this
And the Jasmine test spec like this:
var albumData = [{
"title": "Album A",
"artist": "Artist A",
"tracks": [
{
"title": "Track A",
"url": "/music/Album A Track A.mp3"
},
{
"title": "Track B",
"url": "/music/Album A Track B.mp3"
}]
}, {
"title": "Album B",
"artist": "Artist B",
"tracks": [
{
"title": "Track A",
"url": "/music/Album B Track A.mp3"
},
{
"title": "Track B",
"url": "/music/Album B Track B.mp3"
}]
}];
describe("Album", function () {
beforeEach(function () {
album = new Album(albumData[0]);
});
it("creates from data", function () {
expect(this.album.get('tracks').length).toEqual(2);
});
describe("first track", function() {
it("identifies correct first track", function() {
expect(this.album.isFirstTrack(0)).toBeTruthy();
})
});
});
I'm guessing the problem has something to do with CoffeeScript wrapping everything in a function. When I remove jQuery from the equation it works fine.
Strangely though even though Jasmine is telling me TypeError: Cannot call method 'isFirstTrack' of undefined
if I run album.isFirstTrack(0)
in the console on the page it returns true
so the window does have access to the variables and methods.
Upvotes: 0
Views: 1588
Reputation: 77416
(Note: I believe Derick's answer is correct—it explains the TypeError: Cannot call method 'isFirstTrack' of undefined
message—but this answer may also be pertinent.)
You say
When I remove jQuery from the equation it works fine.
Meaning that if you cut out the line jQuery ->
, then your tests pass? If that's the case, it's not a scope issue; it's a code order issue. When you pass a function to jQuery, that function isn't executed until the document is ready. Meanwhile, your tests are running immediately—before the Album
and AlbumView
classes are defined.
There's no reason to use the jQuery ->
wrapper, since your class definitions don't depend on any existing DOM elements.
Upvotes: 2
Reputation: 72868
the reason is scoping, yes.
when you declare the album
variable with no var
keyword and without attaching it to another object, you are creating a variable in the global scope.
this is a bad idea. you really want to limit the number of global objects that you create, because it's easy for another chunk of javascript to overwrite your data, or cause conflicts, etc.
your idea of calling this.model
in the tests is correct from this perspective. the problem is that you need to attach model
to this
to be able to do that. easy enough:
beforeEach(function () {
this.album = new Album(albumData[0]);
});
all you have to do is say this.album = ...
and you're done. now your tests will be able to find this.album
and everything should work.
Upvotes: 2