Last active
December 15, 2015 00:09
-
-
Save funkatron/5170838 to your computer and use it in GitHub Desktop.
Showing a refactor to improve testability of a JavaScript app's global class. Before, we were doing a bunch of things inside one scope, and doing all the setup + kickstarting the app in the same scope. By breaking things out into separate chunks via function definitions, we can test each piece of setup individually without kickstarting the entir…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script type="text/javascript" src="/static/js/testable_app_run.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define([ | |
'libs/jquery', | |
'libs/backbone', | |
'libs/handlebars', | |
'util/dnd_util', | |
'util/dnd_consts', | |
'routers/app_router', | |
'views/topnav_view', | |
'views/flashmessages_view', | |
'views/footer_view', | |
'models/prefs_model' | |
], function($, Backbone, Handlebars, DndUtil, DndConsts, AppRouter, TopnavView, FlashmessagesView, FooterView, PrefsModel) { | |
/** | |
* establish the app namespace | |
* @type {Object} | |
*/ | |
var DndApp = { | |
attachToWindow: function() { | |
// make the DndApp global in the browser. THIS IS INTENTIONAL | |
window.DndApp = DndApp; | |
}, | |
setupDefaultAvatars: function() { | |
DndApp.DEFAULT_AVATAR_URL = 'https://donenotdone.com/static/img/noavatar-large.gif'; | |
DndApp.DEFAULT_AVATAR_URL_SMALL = 'https://donenotdone.com/static/img/noavatar-small.gif'; | |
DndApp.DEFAULT_AVATAR_URL_MEDIUM = 'https://donenotdone.com/static/img/noavatar-medium.gif'; | |
}, | |
initVent: function() { | |
DndApp.vent = _.extend({}, Backbone.Events); | |
}, | |
attachUtil: function() { | |
// set up a utility method namespace | |
DndApp.util = DndUtil; | |
}, | |
attachConsts: function() { | |
// hook up constants | |
DndApp.consts = DndConsts; | |
}, | |
setupConsole: function() { | |
// turn on/off the console based on window.DNDAPP_CONSOLE_ENABLED | |
DndApp.util.setupConsole(); | |
}, | |
bindAppEvents: function() { | |
// bind some events | |
DndApp.vent.on('api:error', function(code, url, message, xhr) { | |
// don't raise an error if this is a follower check | |
var regex_follow = /\/api\/users\/[a-z0-9_-]+\/following\/[a-z0-9_-]+/i; | |
if (code === 404 && url.match(regex_follow)) { | |
return; | |
} | |
console.error("API ERROR:", code, url, message, xhr); | |
DndApp.vent.trigger('flashmessage:add', | |
"Something went wrong while talking to the server. You might want to reload the page.", | |
"error"); | |
}); | |
}, | |
bindDOMEvents: function() { | |
/** | |
* Bind some global events into vent triggers | |
* TODO This is hackey. I don't like binding stuff like this. We should | |
* do proper work with views and extending base objects | |
*/ | |
$('#modal').on('click', function(e) { | |
if (e.target === e.currentTarget) { // we don't want children | |
DndApp.vent.trigger('click:modal', e); | |
} | |
}); | |
/** | |
* bind a global jQuery ajax error listener. This will proxy the errors | |
* to an app.vent 'api:error' | |
*/ | |
$(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError){ | |
DndApp.vent.trigger('api:error', jqXHR.status, ajaxSettings.url, jqXHR.responseText, jqXHR); | |
}); | |
}, | |
loadBootstrap: function() { | |
// connect jsbootstrap to app | |
DndApp.bootstrap = window.jsbootstrap; | |
// connect to flashmessages | |
DndApp.flashmessages = window.jsflashmessages; | |
DndApp.prefs = new PrefsModel(DndApp.bootstrap.prefs, | |
DndApp.bootstrap.logged_in_user_id); | |
}, | |
initAppSection: function() { | |
/** | |
* the current section of the app | |
* | |
* use DndApp.util.app_section() to get | |
* use DndApp.util.app_section('foo') to set | |
* | |
* @type {string} | |
*/ | |
DndApp.app_section = null; | |
}, | |
initRouter: function() { | |
// instantiate router | |
var app_router = new AppRouter(); | |
// connect to app router | |
DndApp.router = app_router; | |
}, | |
initCommonViews: function() { | |
// These appears everywhere in the app, so just instantiate them here | |
var topnav_view = new TopnavView(); | |
var flashmessages_view = new FlashmessagesView(); | |
var footer_view = new FooterView(); | |
}, | |
startHistory: function() { | |
/** | |
* KICKSTART MY HEART | |
*/ | |
Backbone.history.start({pushState: true, root: DndApp.router.HISTORY_ROOT}); | |
}, | |
initialize: function() { | |
DndApp.attachToWindow(); | |
DndApp.setupDefaultAvatars(); | |
DndApp.initVent(); | |
DndApp.attachUtil(); | |
DndApp.attachConsts(); | |
DndApp.setupConsole(); | |
DndApp.bindDOMEvents(); | |
DndApp.loadBootstrap(); | |
DndApp.initAppSection(); | |
DndApp.initRouter(); | |
DndApp.bindAppEvents(); | |
DndApp.initCommonViews(); | |
}, | |
run: function() { | |
DndApp.startHistory(); | |
} | |
}; | |
return DndApp; | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require.config({ | |
baseUrl: "/static/js" | |
}); | |
require([ | |
'testable_app' | |
], function(DndApp) { | |
DndApp.initialize(); | |
window.DndApp.run(); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script type="text/javascript" src="/static/js/untestable_app.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require.config({ | |
baseUrl: "/static/js" | |
}); | |
define([ | |
'libs/jquery', | |
'libs/backbone', | |
'libs/handlebars', | |
'util/dnd_util', | |
'util/dnd_consts', | |
'routers/app_router', | |
'views/topnav_view', | |
'views/flashmessages_view', | |
'views/footer_view', | |
'models/prefs_model' | |
], function($, Backbone, Handlebars, DndUtil, DndConsts, AppRouter, TopnavView, FlashmessagesView, FooterView, PrefsModel) { | |
/** | |
* establish the app namespace | |
* @type {Object} | |
*/ | |
var DndApp = {}; | |
// make the DndApp global in the browser. THIS IS INTENTIONAL | |
window.DndApp = DndApp; | |
DndApp.DEFAULT_AVATAR_URL = 'https://donenotdone.com/static/img/noavatar-large.gif'; | |
DndApp.DEFAULT_AVATAR_URL_SMALL = 'https://donenotdone.com/static/img/noavatar-small.gif'; | |
DndApp.DEFAULT_AVATAR_URL_MEDIUM = 'https://donenotdone.com/static/img/noavatar-medium.gif'; | |
// attach a global event listener | |
DndApp.vent = _.extend({}, Backbone.Events); | |
// set up a utility method namespace | |
DndApp.util = DndUtil; | |
// hook up constants | |
DndApp.consts = DndConsts; | |
// turn on/off the console based on window.DNDAPP_CONSOLE_ENABLED | |
DndApp.util.setupConsole(); | |
/** | |
* Bind some global events into vent triggers | |
* TODO This is hackey. I don't like binding stuff like this. We should | |
* do proper work with views and extending base objects | |
*/ | |
$('#modal').on('click', function(e) { | |
if (e.target === e.currentTarget) { // we don't want children | |
DndApp.vent.trigger('click:modal', e); | |
} | |
}); | |
/** | |
* bind a global jQuery ajax error listener. This will proxy the errors | |
* to an app.vent 'api:error' | |
*/ | |
$(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError){ | |
DndApp.vent.trigger('api:error', jqXHR.status, ajaxSettings.url, jqXHR.responseText, jqXHR); | |
}); | |
// connect jsbootstrap to app | |
DndApp.bootstrap = window.jsbootstrap; | |
// connect to flashmessages | |
DndApp.flashmessages = window.jsflashmessages; | |
DndApp.prefs = new PrefsModel(DndApp.bootstrap.prefs, | |
DndApp.bootstrap.logged_in_user_id); | |
/** | |
* the current section of the app | |
* | |
* use DndApp.util.app_section() to get | |
* use DndApp.util.app_section('foo') to set | |
* | |
* @type {string} | |
*/ | |
DndApp.app_section = null; | |
// instantiate router | |
var app_router = new AppRouter(); | |
// connect to app router | |
DndApp.router = app_router; | |
// bind some events | |
DndApp.vent.on('api:error', function(code, url, message, xhr) { | |
// don't raise an error if this is a follower check | |
var regex_follow = /\/api\/users\/[a-z0-9_-]+\/following\/[a-z0-9_-]+/i; | |
if (code === 404 && url.match(regex_follow)) { | |
return; | |
} | |
console.error("API ERROR:", code, url, message, xhr); | |
DndApp.vent.trigger('flashmessage:add', | |
"Something went wrong while talking to the server. You might want to reload the page.", | |
"error"); | |
}); | |
// These appears everywhere in the app, so just instantiate them here | |
var topnav_view = new TopnavView(); | |
var flashmessages_view = new FlashmessagesView(); | |
var footer_view = new FooterView(); | |
/** | |
* KICKSTART MY HEART | |
*/ | |
Backbone.history.start({pushState: true, root: DndApp.router.HISTORY_ROOT}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment