Skip to content

Commit

Permalink
Make core modules disposable
Browse files Browse the repository at this point in the history
Moar tests
Testing the AppView
  • Loading branch information
molily committed Apr 14, 2012
1 parent d833dfd commit 610a5cc
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 48 deletions.
27 changes: 26 additions & 1 deletion coffee/chaplin/application.coffee
Expand Up @@ -7,12 +7,19 @@ define [
], (mediator, ApplicationController, ApplicationView, Router) ->
'use strict'

# The application bootstrapper.
# The application bootstrapper
# ----------------------------

class Application

# The site title used in the document title
title: ''

# The application instantiates these three core modules
applicationController: null
applicationView: null
router: null

initialize: ->
#console.debug 'Application#initialize'

Expand All @@ -38,3 +45,21 @@ define [

# After registering the routes, start Backbone.history
@router.startHistory()

# Disposal
# --------

disposed: false

dispose: ->
return if @disposed

properties = ['applicationController', 'applicationView', 'router']
for prop in properties
this[prop].dispose()
delete this[prop]

@disposed = true

# Your're frozen when your heart’s not open
Object.freeze? this
15 changes: 15 additions & 0 deletions coffee/chaplin/controllers/application_controller.coffee
Expand Up @@ -149,3 +149,18 @@ define [

# Save the URL
@url = url

# Disposal
# --------

disposed: false

dispose: ->
return if @disposed

@unsubscribeAllEvents()

@disposed = true

# Your're frozen when your heart’s not open
Object.freeze? this
3 changes: 1 addition & 2 deletions coffee/chaplin/controllers/controller.coffee
Expand Up @@ -22,9 +22,8 @@ define [

initialize: ->

#
# Disposal
#
# --------

disposed: false

Expand Down
17 changes: 17 additions & 0 deletions coffee/chaplin/views/application_view.coffee
Expand Up @@ -154,3 +154,20 @@ define [
else
# Navigate to the URL normally
location.href = path

# Disposal
# --------

disposed: false

dispose: ->
return if @disposed

@unsubscribeAllEvents()

delete @title

@disposed = true

# Your're frozen when your heart’s not open
Object.freeze? this
10 changes: 6 additions & 4 deletions coffee/chaplin/views/view.coffee
Expand Up @@ -126,14 +126,16 @@ define [
else if arguments.length is 3
selector = second
if typeof selector isnt 'string'
throw new TypeError 'View#delegate: second argument must be a string'
throw new TypeError 'View#delegate: ' +
'second argument must be a string'
handler = third
else
throw new TypeError 'View#delegate: only two or three arguments are
allowed'
throw new TypeError 'View#delegate: ' +
'only two or three arguments are allowed'

if typeof handler isnt 'function'
throw new TypeError 'View#delegate: handler argument must be function'
throw new TypeError 'View#delegate: ' +
'handler argument must be function'

# Add an event namespace
eventType += ".delegate#{@cid}"
Expand Down
21 changes: 21 additions & 0 deletions js/chaplin/application.js
Expand Up @@ -8,6 +8,12 @@ define(['mediator', 'chaplin/controllers/application_controller', 'chaplin/views

Application.prototype.title = '';

Application.prototype.applicationController = null;

Application.prototype.applicationView = null;

Application.prototype.router = null;

Application.prototype.initialize = function() {
this.applicationController = new ApplicationController();
return this.applicationView = new ApplicationView({
Expand All @@ -21,6 +27,21 @@ define(['mediator', 'chaplin/controllers/application_controller', 'chaplin/views
return this.router.startHistory();
};

Application.prototype.disposed = false;

Application.prototype.dispose = function() {
var prop, properties, _i, _len;
if (this.disposed) return;
properties = ['applicationController', 'applicationView', 'router'];
for (_i = 0, _len = properties.length; _i < _len; _i++) {
prop = properties[_i];
this[prop].dispose();
delete this[prop];
}
this.disposed = true;
return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
};

return Application;

})();
Expand Down
9 changes: 9 additions & 0 deletions js/chaplin/controllers/application_controller.js
Expand Up @@ -84,6 +84,15 @@ define(['mediator', 'chaplin/lib/utils', 'chaplin/lib/subscriber'], function(med
return this.url = url;
};

ApplicationController.prototype.disposed = false;

ApplicationController.prototype.dispose = function() {
if (this.disposed) return;
this.unsubscribeAllEvents();
this.disposed = true;
return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
};

return ApplicationController;

})();
Expand Down
10 changes: 10 additions & 0 deletions js/chaplin/views/application_view.js
Expand Up @@ -103,6 +103,16 @@ define(['mediator', 'chaplin/lib/utils', 'chaplin/lib/subscriber'], function(med
});
};

ApplicationView.prototype.disposed = false;

ApplicationView.prototype.dispose = function() {
if (this.disposed) return;
this.unsubscribeAllEvents();
delete this.title;
this.disposed = true;
return typeof Object.freeze === "function" ? Object.freeze(this) : void 0;
};

return ApplicationView;

})();
Expand Down
7 changes: 3 additions & 4 deletions js/chaplin/views/view.js
Expand Up @@ -70,15 +70,14 @@ define(['chaplin/lib/utils', 'chaplin/lib/subscriber', 'chaplin/lib/view_helper'
} else if (arguments.length === 3) {
selector = second;
if (typeof selector !== 'string') {
throw new TypeError('View#delegate: second argument must be a string');
throw new TypeError('View#delegate: ' + 'second argument must be a string');
}
handler = third;
} else {
throw new TypeError('View#delegate: only two or three arguments are\
allowed');
throw new TypeError('View#delegate: ' + 'only two or three arguments are allowed');
}
if (typeof handler !== 'function') {
throw new TypeError('View#delegate: handler argument must be function');
throw new TypeError('View#delegate: ' + 'handler argument must be function');
}
eventType += ".delegate" + this.cid;
handler = _(handler).bind(this);
Expand Down
23 changes: 19 additions & 4 deletions test/spec/coffee/application_controller_spec.coffee
Expand Up @@ -8,14 +8,21 @@ define [
describe 'ApplicationController', ->
#console.debug 'ApplicationController spec'

# Initialize shared variables
applicationController = undefined

initializeCalled = actionCalled =
historyURLCalled = disposeCalled = undefined
params = passedParams = undefined

# Unique ID counter for creating params objects
paramsId = 0

# Fake route object, walks like a route and swims like a route
route = controller: 'test', action: 'show'

# Reset helpers

resetFlags = ->
initializeCalled = actionCalled =
historyURLCalled = disposeCalled = false
Expand All @@ -25,9 +32,6 @@ define [
params = changeURL: false, id: paramsId++
passedParams = undefined

# Fake route object, walks like a route and swims like a route
route = controller: 'test', action: 'show'

# Clear the mediator
mediator.unsubscribe()

Expand Down Expand Up @@ -91,7 +95,7 @@ define [
expect(c.currentParams).toBe params
expect(c.url).toBe "test/#{params.id}"

it 'should dispose old controllers', ->
it 'should dispose inactive controllers', ->
passedController = undefined
beforeControllerDispose = (controller) ->
passedController = controller
Expand Down Expand Up @@ -119,3 +123,14 @@ define [
expect(passedEvent.previousControllerName).toBe 'test'

mediator.unsubscribe 'startupController', startupController

it 'should be disposable', ->
expect(typeof applicationController.dispose).toBe 'function'
applicationController.dispose()

mediator.publish 'matchRoute', route, params
expect(initializeCalled).toBe false

expect(applicationController.disposed).toBe true
if Object.isFrozen
expect(Object.isFrozen(applicationController)).toBe true
15 changes: 13 additions & 2 deletions test/spec/coffee/application_spec.coffee
Expand Up @@ -7,11 +7,11 @@ define [
], (mediator, Application, Router, ApplicationController, ApplicationView) ->
'use strict'

application = new Application()

describe 'Application', ->
#console.debug 'Application spec'

application = new Application()

it 'should be a simple object', ->
expect(typeof application).toEqual 'object'
expect(application instanceof Application).toBe true
Expand Down Expand Up @@ -44,3 +44,14 @@ define [

it 'should start Backbone.history', ->
expect(Backbone.History.started).toBe true

it 'should be disposable', ->
expect(typeof application.dispose).toBe 'function'
application.dispose()
expect(application.applicationController).toBe null
expect(application.applicationView).toBe null
expect(application.router).toBe null

expect(application.disposed).toBe true
if Object.isFrozen
expect(Object.isFrozen(application)).toBe true
98 changes: 88 additions & 10 deletions test/spec/coffee/application_view_spec.coffee
@@ -1,20 +1,98 @@
define [
'mediator',
'chaplin/controllers/controller',
'chaplin/views/application_view'
], (mediator, Controller, ApplicationView) ->
'chaplin/views/application_view',
'chaplin/views/view'
], (mediator, Controller, ApplicationView, View) ->
'use strict'

describe 'ApplicationView', ->
applicationView = undefined
applicationView = testController = startupControllerContext = undefined

# Clear the mediator
mediator.unsubscribe()
beforeEach ->
# Create the application view
applicationView.dispose() if applicationView
applicationView = new ApplicationView title: 'Test Site Title'

testController = new Controller()
# Create a test controller
testController = new Controller()
testController.view = new View()
testController.title = 'Test Controller Title'

it 'should initialize', ->
applicationView = new ApplicationView()
# Payload for startupController event
startupControllerContext =
previousControllerName: 'null'
controller: testController
controllerName: 'test'
params: {}

xit 'should be tested more thoroughly', ->
expect(false).toBe true
it 'should hide the view of an inactive controller', ->
testController.view.$el.css 'display', 'block'
mediator.publish 'beforeControllerDispose', testController
expect(testController.view.$el.css('display')).toBe 'none'

it 'should show the view of the active controller', ->
testController.view.$el.css 'display', 'none'
mediator.publish 'startupController', startupControllerContext
$el = testController.view.$el
expect($el.css('display')).toBe 'block'
expect($el.css('opacity')).toBe '1'
expect($el.css('visibility')).toBe 'visible'

it 'should hide accessible fallback content', ->
$(document.body).append(
'<p class="accessible-fallback" style="display: none">Accessible fallback</p>'
)
mediator.publish 'startupController', startupControllerContext
expect($('.accessible-fallback').length).toBe 0

it 'should set the document title', ->
runs ->
mediator.publish 'startupController', startupControllerContext
waits 100
runs ->
title = "#{testController.title} \u2013 #{applicationView.title}"
expect(document.title).toBe title

it 'should set logged-in/logged-out body classes', ->
$body = $(document.body).attr('class', '')

mediator.publish 'loginStatus', true
expect($body.attr('class')).toBe 'logged-in'

mediator.publish 'loginStatus', false
expect($body.attr('class')).toBe 'logged-out'

it 'should route clicks on internal links', ->
passedPath = passedCallback = undefined
routerRoute = (path, callback) ->
passedPath = path
passedCallback = callback

mediator.subscribe '!router:route', routerRoute
path = '/an/internal/link'
$("<a href='#{path}'>")
.appendTo(document.body)
.click()
.remove()
expect(passedPath).toBe path
expect(typeof passedCallback).toBe 'function'
mediator.unsubscribe '!router:route', routerRoute

spy = jasmine.createSpy()
mediator.subscribe '!router:route', spy
path = 'http://www.example.org/'
$("<a href='#{path}'>")
.appendTo(document.body)
.click()
.remove()
expect(spy).not.toHaveBeenCalled()
mediator.unsubscribe '!router:route', spy

it 'should be disposable', ->
expect(typeof applicationView.dispose).toBe 'function'
applicationView.dispose()

expect(applicationView.disposed).toBe true
if Object.isFrozen
expect(Object.isFrozen(applicationView)).toBe true

0 comments on commit 610a5cc

Please sign in to comment.