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

Jasmine for Rubyists @ #rockymtnruby with Cory Flanigan & Justin Searls

E6c6e133e74c3b83f04d2861deaa1c20?s=47 Justin Searls
September 02, 2011

Jasmine for Rubyists @ #rockymtnruby with Cory Flanigan & Justin Searls

Presented at Rocky Mountain Ruby on September 2, 2011.

E6c6e133e74c3b83f04d2861deaa1c20?s=128

Justin Searls

September 02, 2011
Tweet

Transcript

  1. oh hi, I didn’t see you there

  2. Ruby:Craftsmanship::JavaScript:________

  3. Ruby:Craftsmanship::JavaScript:FFFUUU

  4. why?

  5. why? it’s hard

  6. why? it’s hard it’s just glue code

  7. why? it’s hard it’s just glue code it’s covered by

    selenium
  8. why? it’s hard it’s just glue code it’s covered by

    selenium too many tools, not enough convention
  9. why? it’s hard it’s just glue code it’s covered by

    selenium too many tools, not enough convention it’s just UI flair
  10. why? it’s hard it’s just glue code it’s covered by

    selenium too many tools, not enough convention it’s just UI flair page refresh-driven development
  11. why? it’s hard no one expects me to do it

    it’s just glue code it’s covered by selenium too many tools, not enough convention it’s just UI flair page refresh-driven development
  12. None
  13. None
  14. consequences

  15. sacrificing the quality of the user’s experience for the sake

    of our own familiar practices
  16. None
  17. function(){ return function(){ return function(){ require ‘BBOM.js’ }; }; };

    application.js
  18. function loadIt(){ /* This separate function simply saves parsing the

    database for each search. The arrayflg is used to flag that it has been done. */ arrayflg=1; /* First, read in the string from the hidden form element. */ ls=document.ug.ly.value; ctr=0; /* Then parse the long string and load it into the array. */ while (ls.indexOf("*")>-1){ pos=ls.indexOf("*"); db[ctr]=ls.substring(0,pos); /* Similarly, no need to repeatedly convert the strings in the array to lower case. Once is enough. */ dblc[ctr]=db[ctr].toLowerCase(); ls=ls.substring(pos+1,ls.length); ctr++; } } so many comments. What does it do? This code has /* This separate function simply saves parsing the database for each search. The arrayflg is used to flag that it has been done. */ /* First, read in the string from the hidden form element. */ /* Then parse the long string and load it into the array. */ /* Similarly, no need to repeatedly convert the strings in the array to lower case. Once is enough. */ function loadIt(){ arrayflg=1; ls=document.ug.ly.value; ctr=0; while (ls.indexOf("*")>-1){ pos=ls.indexOf("*"); db[ctr]=ls.substring(0,pos); dblc[ctr]=db[ctr].toLowerCase(); ls=ls.substring(pos+1,ls.length); ctr++; } }
  19. avoiding front-end work

  20. “ “I basically never pair.” http://is.gd/pairing Jay Fields

  21. But that’s not what he really said

  22. “experience experience experience context context context context context context context

    context context context context context caveats caveats caveats caveats “I basically never pair.” http://is.gd/pairing Jay Fields
  23. “experience experience experience context context context context context context context

    context context context context context caveats caveats caveats caveats “I don’t do JavaScript.” Unknown
  24. Please Start testing your JavaScript before you quit testing your

    JavaScript.
  25. ...teach them how to fish. So, we could rescue them,

    or... they’re drowning in a sea of untested code! I see.
  26. codes!

  27. Know RSpec?

  28. describe “RSpec” do it “looks like Jasmine” do self.should be_familiar

    end end
  29. describe(“Jasmine”, function(){ it(“looks like RSpec”, function(){ expect(this).toBeFamiliar(); }); });

  30. describe(“Jasmine”, function(){ it(“looks like RSpec”, function(){ expect(this).toBeFamiliar(); }); }); It

    looks like you’re trying to write RSpec
  31. Now you know Jasmine!

  32. None
  33. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); });

  34. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); }); what’s the concern?

  35. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); }); what’s the concern?

  36. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); }); what’s the concern?

  37. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); }); what’s the concern?

  38. and it leads to...

  39. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); });

  40. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); }); $(‘.delete-task’).live(‘click’,function(e) { e.preventDefault(); $(this).remove();

    });
  41. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); }); $(‘.delete-task’).live(‘click’,function(e) { e.preventDefault(); $(this).remove();

    }); $(‘.show-task’).live(‘click’,function(e) { e.preventDefault(); $(this).show(); });
  42. $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); }); $(‘.delete-task’).live(‘click’,function(e) { e.preventDefault(); $(this).remove();

    }); $(‘.show-task’).live(‘click’,function(e) { e.preventDefault(); $(this).show(); });
  43. how might we spec this behavior?

  44. describe('#addTask', function(){ var $container; beforeEach(function(){ $container = $('<div></div>'); addTask.call($container); });

    it('appends a div', function(){ expect($container).toContain('div'); }); it('includes a default text label', function(){ expect($container).toHaveText('New Task!'); }); });
  45. describe('#addTask', function(){ var $container; beforeEach(function(){ $container = $('<div></div>'); addTask.call($container); });

    it('appends a div', function(){ expect($container).toContain('div'); }); it('includes a default text label', function(){ expect($container).toHaveText('New Task!'); }); }); jasmine-jquery http://is.gd/jasminejquery
  46. that spec drove...

  47. var addTask = function(){ this.append('<div>New Task!</div>'); };

  48. how might we spec event binding?

  49. describe('#clicker',function(){ var handler, event; beforeEach(function(){ handler = jasmine.createSpy('handles clicks'); event

    = $.Event('click'); spyOn(event,'preventDefault'); $.jasmine.inject('<div class="my-button"></div>'); }); context('when you click the button',function(){ beforeEach(function(){ clicker('.my-button',handler); $('.my-button').trigger(event); }); it('triggers the event handler',function(){ expect(handler).toHaveBeenCalledWith(event); }); it('binds `this` to the jQuery result object',function(){ expect(handler.mostRecentCall.object).toBe('.my-button'); }); it('prevents default browser behavior',function(){ expect(event.preventDefault).toHaveBeenCalled(); }); }); });
  50. describe('#clicker',function(){ var handler, event; beforeEach(function(){ handler = jasmine.createSpy('handles clicks'); event

    = $.Event('click'); spyOn(event,'preventDefault'); $.jasmine.inject('<div class="my-button"></div>'); }); context('when you click the button',function(){ beforeEach(function(){ clicker('.my-button',handler); $('.my-button').trigger(event); }); it('triggers the event handler',function(){ expect(handler).toHaveBeenCalledWith(event); }); it('binds `this` to the jQuery result object',function(){ expect(handler.mostRecentCall.object).toBe('.my-button'); }); it('prevents default browser behavior',function(){ expect(event.preventDefault).toHaveBeenCalled(); }); }); });
  51. describe('#clicker',function(){ var handler, event; beforeEach(function(){ handler = jasmine.createSpy('handles clicks'); event

    = $.Event('click'); spyOn(event,'preventDefault'); $.jasmine.inject('<div class="my-button"></div>'); }); context('when you click the button',function(){ beforeEach(function(){ clicker('.my-button',handler); $('.my-button').trigger(event); }); it('triggers the event handler',function(){ expect(handler).toHaveBeenCalledWith(event); }); it('binds `this` to the jQuery result object',function(){ expect(handler.mostRecentCall.object).toBe('.my-button'); }); it('prevents default browser behavior',function(){ expect(event.preventDefault).toHaveBeenCalled(); }); }); });
  52. describe '#clicker', -> handler=event=null; beforeEach -> handler = jasmine.createSpy 'handles

    clicks' event = $.Event 'click' spyOn event, 'preventDefault' $.jasmine.inject '<div class="my-button"></div>' context 'when you click the button', -> beforeEach -> clicker '.my-button', handler $('.my-button').trigger(event) it 'triggers the event handler', -> expect(handler).toHaveBeenCalledWith(event) it 'binds `this` to the jQuery result object', -> expect(handler.mostRecentCall.object).toBe('.my-button') it 'prevents default browser behavior', -> expect(event.preventDefault).toHaveBeenCalled()
  53. describe '#clicker', -> handler=event=null; beforeEach -> handler = jasmine.createSpy 'handles

    clicks' event = $.Event 'click' spyOn event, 'preventDefault' $.jasmine.inject '<div class="my-button"></div>' context 'when you click the button', -> beforeEach -> clicker '.my-button', handler $('.my-button').trigger(event) it 'triggers the event handler', -> expect(handler).toHaveBeenCalledWith(event) it 'binds `this` to the jQuery result object', -> expect(handler.mostRecentCall.object).toBe('.my-button') it 'prevents default browser behavior', -> expect(event.preventDefault).toHaveBeenCalled()
  54. describe '#clicker', -> handler=event=null; beforeEach -> handler = jasmine.createSpy 'handles

    clicks' event = $.Event 'click' spyOn event, 'preventDefault' $.jasmine.inject '<div class="my-button"></div>' context 'when you click the button', -> beforeEach -> clicker '.my-button', handler $('.my-button').trigger(event) it 'triggers the event handler', -> expect(handler).toHaveBeenCalledWith(event) it 'binds `this` to the jQuery result object', -> expect(handler.mostRecentCall.object).toBe('.my-button') it 'prevents default browser behavior', -> expect(event.preventDefault).toHaveBeenCalled()
  55. that spec drove...

  56. var clicker = function(selector, whenClicked){ $(selector).live('click',function(e) { e.preventDefault(); whenClicked.call($(this),e); });

    };
  57. clicker('.add-task', addTask); $('.add-task').live('click',function(e) { e.preventDefault(); $(this).append('<div>New Task!</div>'); });

  58. Conclusion: take the next step

  59. try jasmine https://try-jasmine.heroku.com

  60. None
  61. None
  62. try jasmine-rails https://github.com/searls/jasmine-rails

  63. None
  64. None
  65. Consider coming to our non-profit JavaScript Craftsmanshop next week in

    Denver @ Uncubed on 9/8 & 9/9 register at http://is.gd/jasmine Use coupon code RMRB for 25% off (of $95)
  66. Consider coming to our non-profit JavaScript Craftsmanshop next week in

    Denver @ Uncubed on 9/8 & 9/9 register at http://is.gd/jasmine Use coupon code RMRB for 25% off (of $95) 2 days for 71-ish dollars
  67. ask us questions and we’ll try to help

  68. thank you! searls@gmail.com Rocky Mountain Ruby, Boulder, CO September 2,

    2011 @searls seeflanigan@gmail.com @seeflanigan Thanks to Brandon Keepers (@bkeepers) for the Keynote template! find this talk at http://is.gd/rockyjasmine