Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KA JavaScript Modules and Testing

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

KA JavaScript Modules and Testing

Exalted Talk on JS Modules and Testing best practices

Avatar for Jamie Wong

Jamie Wong

July 16, 2014

Other Decks in Programming

Transcript

  1. What you need to know • We implement a strict

    subset of node's require() • You can't use normal node_modules (yet) • package contents are still dictated by javascript-packages.json (sorry!)
  2. Not use global variables // javascript/shared-package/visit-tracking.js window.VisitTracking = {
 };

    ! ! ! // javascript/shared-package/analytics.js VisitTracking.visit(); !
  3. Not use global variables // javascript/shared-package/visit-tracking.js var VisitTracking = {


    }; ! module.exports = VisitTracking; ! ! ! // javascript/shared-package/analytics.js var VisitTracking = require("./visit-tracking.js"); ! VisitTracking.visit(); !
  4. Not use global variables // javascript/react-package/checkbox.jsx ! /** @jsx React.DOM

    */ var CheckBox = React.createClass({ ... render: function() { return <span onClick={this.props.toggle} className={this.props.className}> <i className={"icon-check" + this.decoration()} /> {this.props.children} </span>; } }); ! module.exports = CheckBox; ! // javascript/reports-package/filterbar.jsx ... var CheckBox = require("../react-package/checkbox.jsx"); ...
  5. Be side effect-less // javascript/shared-package/mymodule.js var MyModule = { init:

    function() { // Fire some AJAX requests to load some content // Set up DOM elements // Add stuff to localStorage
 }
 }; ! MyModule.init(); ! module.exports = MyModule;
  6. Be side effect-less // javascript/shared-package/mymodule.js var MyModule = { init:

    function() { // Fire some AJAX requests to load some content // Set up DOM elements // Add stuff to localStorage
 }
 }; ! module.exports = MyModule; ! // javascript/shared-package/pageutil.js var MyModule = require("./mymodule.js"); ! MyModule.init();
  7. Export a single object // javascript/widgets-package/widgets.js var TitleBarWidget = {


    }; ! var ScrollBarWidget = { }; ! var ModalWidget = { }; ! module.exports = {
 TitleBarWidget: TitleBarWidget, ScrollBarWidget: ScrollBarWidget, ModalWidget: ModalWidget };
  8. Export a single object // javascript/widgets-package/title-bar-widget.js var TitleBarWidget = {


    }; module.exports = TitleBarWidget; ! // javascript/widgets-package/scroll-bar-widget.js var ScrollBarWidget = { }; module.exports = ScrollBarWidget; ! // javascript/widgets-package/modal-widget.js var ModalWidget = { }; module.exports = ModalWidget;
  9. Not use namespacing // javascript/widgets-package/title-bar-widget.js window.Widgets = window.Widgets || {};

    ! Widgets.TitleBar = { }; ! // javascript/widgets-package/scroll-bar-widget.js; window.Widgets = window.Widgets || {}; ! Widgets.ScrollBar = {
 };
  10. Not mutate other modules // javascript/widgets-package/index.js var Widgets = {};

    ! module.exports = Widgets; ! // javascript/widgets-package/title-bar-widget.js _.extend(require("./index.js"), { TitleBar: {
 }
 }); ! // javascript/widgets-package/scroll-bar-widget.js; _.extend(require("./scroll-bar-widget.js"), { ScrollBar: {
 }
 });
  11. This is a test // javascript/shared-package/regex-util_test.js var testutil = require("../testutil.js");

    var RegexUtil = require("./regex-util.js"); ! var describe = testutil.describe; var it = testutil.it; var assert = testutil.assert; ! describe("RegexUtil", function() { describe("escapeRegex", function() { it("escapes regex special characters", function() { var eMatch = function(pattern, toMatch) { var re = new RegExp(RegexUtil.escapeRegex(pattern)); return re.test(toMatch); }; ! assert.isTrue(eMatch("khanacademy.org", "khanacademy.org")); assert.isFalse(eMatch("khanacademy.org", "khanacademy@org")); assert.isTrue(eMatch(":)", ":)")); assert.isTrue(eMatch("a[0]", "a[0]")); assert.isFalse(eMatch("a[0]", "a0")); }); }); });
  12. What you need to know • You can write them

    today! • Run tests in the console tools/runjstests.py • Run tests in browser tools/runjstests.py -r browser
  13. What else you need to know • Failing JavaScript tests

    block deploys • You might need to add missing require() calls to write tests • bundle contents are not dictated by javascript-packages.json in tests
  14. Be side effect-less // javascript/shared-package/encourager_test.js ... window.KA = {
 getUserId:

    function() {
 return "sal"; } }; ! describe("Encourager", function() { describe(".motivationalMessage", function() { it("returns an encouraging message for the user", function() { assert.equal(Encourager.message(), "You can do it, sal!");
 });
 });
 });
  15. Be side effect-less // javascript/shared-package/encourager_test.js ... describe("Encourager", function() { var

    KAbackup; beforeEach(function() { KAbackup = window.KA; window.KA = {
 getUserId: function() {
 return "sal"; } };
 }); afterEach(function() { window.KA = KAbackup;
 }); describe(".motivationalMessage", function() { it("returns an encouraging message for the user", function() { assert.equal(Encourager.message(), "You can do it, sal!");
 });
 });
 });
  16. Mock timers // javascript/shared-package/later.js var later = function(cb, arg) {

    window.setTimeout(function() { cb(arg); }, 1000); }; ! // javascript/shared-package/later_test.js ... describe("later", function() { it("runs the callback later", function(done) { var cb = function(arg) { assert.equal(arg, "hello"); done();
 }; later(cb, "hello");
 });
 });
  17. Mock timers // javascript/shared-package/later.js var later = function(cb, arg) {

    window.setTimeout(function() { cb(arg); }, 1000); }; ! // javascript/shared-package/later_test.js ... describe("later", function() { var sandbox; beforeEach(function() { sandbox = sinon.sandbox.create(); sandbox.useFakeTimers(); }); afterEach(function() { sandbox.restore();
 }); it("runs the callback later", function() { var cb = sinon.spy(function(arg) { assert.equal(arg, "hello"); }); later(cb, "hello"); sandbox.clock.tick(1000); assert.ok(cb.called);
 });
 });
  18. Mock AJAX requests // javascript/shared-package/weather.js var weather = function(cb) {

    $.get("/api/weather.json", cb);
 }; ! // javascript/shared-package/weather_test.js ... describe("weather", function() { var sandbox; beforeEach(function() { sandbox = sinon.sandbox.create(); sandbox.useFakeServer(); }); afterEach(function() { sandbox.restore();
 }); it("hits the /api/weather.json endpoint", function() { sandbox.server.respondWith(JSON.stringify({sunny: true})); var cb = sinon.spy(function(weather) { assert.ok(weather.sunny); }); weather(cb); sandbox.server.respond(); assert.ok(cb.called);
 });
 });