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

Write your jQuery in console

Write your jQuery in console

A presentation on why and how we moved to unit testing our jQuery code using Node.js.

Marek Kalnik

October 30, 2013
Tweet

More Decks by Marek Kalnik

Other Decks in Programming

Transcript

  1. 3/32 240 000 results for “javascript spaghetti code” 3 times

    more than Python 2 times more than Java 2.5 times less than PHP ;)
  2. 5/32 jQuery now runs 68.8 % of the web* *

    in top 10k websites by builtwith.com
  3. 8/32 We try to replace jQuery by something else Changing

    framework proves to be expensive and inefficient
  4. 11/32 Node.js makes it childishly easy and blazingly fast to

    test JavaScript Photo credit: Viernest / Foter.com / CC BY
  5. 16/32 The methodology • Object-Oriented • TDD : write the

    test first, then the code • continuous testing
  6. 17/32 The modules • jsdom : https://github.com/tmpvar/jsdom • jQuery :

    https://github.com/coolaj86/node-jquery • mocha & expect.js • Guard
  7. 18/32 . ├── Guardfile ├── lib │ └── hider.coffee ├──

    package.json └── spec └── hider_spec.coffee Hider – an object that hides a DOM node
  8. 19/32 # A sample Guardfile # More info at https://github.com/guard/guard#readme

    # Installed by guard-mocha-node guard "mocha-node", :all_after_pass => false, :mocha_bin => File.expand_path(File.dirname(__FILE__) + "/node_modules/mocha/bin/mocha") do watch(%r{^spec/.+_spec\.js$}) watch(%r{^lib/(.+)\.js$}) { |m| "spec/#{m[1]}_spec.js" } end Guardfile
  9. 21/32 var expect = require('expect.js'), hiderModule = require('../lib/hider.coffee'), jsdom =

    require('jsdom'), jQuery = require('jquery'), doc = jsdom.jsdom('<html>' + '<body><p><input id="test" type="checkbox" /></p></body>' + '</html>'), window = doc.parentWindow(); $ = global.jQuery = jQuery.create(window); describe('Test jQuery works', function () { it('Should work', function () { expect($('input').length).to.equal(1); }); describe('Test Hider', function () { selector = '#test'; it('should hide the element when hide is called', function () { hider = new hiderModule.Hider(selector); hider.hide(); expect($(selector).is(':visible')).not.to.be.ok(); }); }); The test
  10. 22/32 var Hider = function(element) { this.element = element; this.hide

    = function() { $(this.element).hide(); }; }; module.exports = { Hider: Hider } And the class!
  11. 26/32 Refactoring methodology - copy-paste the HTML to create your

    DOM document - clean up HTML code to see clearer - focus on one functionality: cover it by tests - refactor your functionality into OO, by adding unit tests and continuously running integration tests to see if they are still green
  12. 27/32 $(document).ready(function() { $('.postalCodePopin').on('change', function(e) { var selectedOption = this.value,

    href = $('#postal-code-popin').data('href'), modal = $('#modal'); if (selectedOption == 'postalcode') { modal.load(href); modal.modal(); } }); $('.row-postalCodeWithAutoComplete').append('<span class="removePostalCode"></span>'); $('.removePostalCode').on('click', function(e) { $(this).prevAll('input').val('').parent().hide().prev('.row-perimeter').show().children('.postalCodeShow').each(function(){ $(this).val($(this).find('option:first').val()); }); }); $('.postalCodeShow').on('change', function(e) { var selectedOption = this.value; if (selectedOption == 'postalcode') { $(this).parent().hide().next('.row-postalCodeWithAutoComplete').show().children('.postalCodeField').focus(); } }); $('.postalCodeField').each(function(){ if ($(this).val() != "") { $(this).parent().show().prev('.row-perimeter').hide(); } }); $('.postalCodeShow').each(function(){ if ($(this).val() == "postalcode") { $(this).parent().hide().next('.row-postalCodeWithAutoComplete').show(); } }); }); Example, the code to refactor
  13. 28/32 describe('hide select', function() { it ('when selected choice should

    hide the select', function() { var $ = createContext(); testedCode.init($); expect($('.row-perimeter').is(':visible')).to.be.ok(); $('#select').val("postalcode").change(); expect($('.row-perimeter').is(':visible')).not.to.be.ok(); }); it ('when the choice is selected the input should show', function() { var $ = createContext(); testedCode.init($); expect($('.row-postalCodeWithAutoComplete').is(':visible')).not.to.be.ok(); $('#select').val("postalcode").change(); expect($('.row-postalCodeWithAutoComplete').is(':visible')).to.be.ok(); }); it ('when close clicked the input should disappear', function() { var $ = createContext(); testedCode.init($); $('#select').val("postalcode").change(); $('.removePostalCode').click(); expect($('.row-perimeter').is(':visible')).to.be.ok(); expect($('.row-postalCodeWithAutoComplete').is(':visible')).not.to.be.ok(); }); }); Example, the test
  14. 29/32 PostalCodeWidget = function(context) { if (typeof context === 'undefined')

    { context = 'body'; } var PLACEHOLDER = 'postalcode', inputContainer = $(context).find('.row-postalCodeWithAutoComplete'), input = inputContainer.find('input'), selectContainer = $(context).find('.row-perimeter'), codesList = selectContainer.find('select'), defaultValue = codesList.val() !== PLACEHOLDER ? select.val() : codesList.find('option:first').val(), removeBtn = inputContainer.append('<span class="removePostalCode"></span>').find('.removePostalCode'), _this = this; this.showSelect = function () { codesList.val(defaultValue); selectContainer.show(); inputContainer.hide(); input.val(''); }; this.showInput = function () { selectContainer.hide(); inputContainer.show(); if (arguments[0] === true) { inputContainer.children('.postalCodeField').focus(); } }; // bindings codesList.on('change', function() { if (this.value === PLACEHOLDER) { _this.showInput(true); } }); removeBtn.on('click', function() { _this.showSelect(); }); // initialize if (input.val() !== '' || select.val() === PLACEHOLDER) { this.showInput(); } }; Example, refactored
  15. 30/32 Not tested: - AJAX - third party libs integration

    - dynamic JS insertion - applying in a project form the beginning
  16. 32/32 Brought to you by @marekkalnik a @theodo developer GitHub

    repository: https://github.com/theodo/nodejs-frontend-dev Thanks for listening!