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.

908a0c79fc96a06684fa7e2a523bde1b?s=128

Marek Kalnik

October 30, 2013
Tweet

Transcript

  1. Write your jQuery in console Marek Kalnik

  2. 2/32 1995 JavaScript is created

  3. 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 ;)
  4. 4/32 2006 jQuery was released

  5. 5/32 jQuery now runs 68.8 % of the web* *

    in top 10k websites by builtwith.com
  6. 6/32 2010 Theodo writes JS code, we are not happy

    with the spaghetti we create
  7. 7/32

  8. 8/32 We try to replace jQuery by something else Changing

    framework proves to be expensive and inefficient
  9. 9/32 Step back...

  10. 10/32 2009 Node.js is released

  11. 11/32 Node.js makes it childishly easy and blazingly fast to

    test JavaScript Photo credit: Viernest / Foter.com / CC BY
  12. 12/32 Idea

  13. 13/32 Testing JavaScript in browser encourages trial and error and

    quick and dirty code
  14. 14/32 Removing browser from the development process will improve the

    code quality
  15. 15/32 The proof of concept

  16. 16/32 The methodology • Object-Oriented • TDD : write the

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

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

    package.json └── spec └── hider_spec.coffee Hider – an object that hides a DOM node
  19. 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
  20. 20/32 A Guard to watch it all guard "mocha-node", :reporter

    => 'nyan', … do
  21. 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
  22. 22/32 var Hider = function(element) { this.element = element; this.hide

    = function() { $(this.element).hide(); }; }; module.exports = { Hider: Hider } And the class!
  23. 23/32 Implement!

  24. 24/32 Tested in playground with satisfying results

  25. 25/32 Applied to two current projects to help with refactoring

    of the JavaScript code
  26. 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
  27. 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
  28. 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
  29. 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
  30. 30/32 Not tested: - AJAX - third party libs integration

    - dynamic JS insertion - applying in a project form the beginning
  31. 31/32 2014 All JavaScript written by Theodo is written using

    Node.js
  32. 32/32 Brought to you by @marekkalnik a @theodo developer GitHub

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