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

Refatorando JavaScript Macarrônico

Refatorando JavaScript Macarrônico

Uma introdução a padrões de projeto em JavaScript com foco em testes unitários automatizados. Um passo-a-passo na refatoração de um código jQuery para componentes testáveis com Jasmine. Palestra apresentada no Café com Dev em Londrina.
https://js-refactoring.herokuapp.com/

Flávio Granero

November 28, 2014
Tweet

More Decks by Flávio Granero

Other Decks in Programming

Transcript

  1. Linguagem mais importante da atualidade Github trending: 10/25 são JavaScript

    Ultra fast engines: FireFox SpiderMonkey, Google V8 and Safari JavaScriptCore Stanford CS101 usando Javascript
  2. 20 anos de idade Revoluções: 1995 - Netscape 2004 -

    AJAX (Gmail) 2006 - Jquery 2009 - Node.js
  3. <!DOCTYPE html>! <html>! <head>! <title>JS Refactoring</title>! <meta charset="utf-8">! <script src="jquery-2.1.1.min.js"></script>!

    <script src="app.js"></script>! <link href="style.css" rel="stylesheet" type="text/css">! </head>! <body>! <div class="container">! <article>! <h1>Refatorando Javascript</h1>! <p>…</p>! </article>! ! <div class="comments">! <h4>Comentários</h4>! <form id="comment-form" method="post" action="/comments">! <textarea rows="5" name="comment"></textarea>! <input type="submit" value="Enviar comentário"/>! </form>! ! <ul id="comment-list"></ul>! </div>! </div>! </body>! </html> <script src="jquery-2.1.1.min.js"></script>! <script src="app.js"></script>! <link href="style.css" rel="stylesheet" type="text/css"> <form id="comment-form" method="post" action="/comments">! <textarea rows="5" name="comment"></textarea>! <input type="submit" value="Enviar comentário"/>! </form> <ul id="comment-list"></ul>!
  4. // app.js! $(document).ready(function() {! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea

    = $(this).find('textarea');! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: $textarea.val() },! success: function() {}! });! });! });
  5. // app.js! $(document).ready(function() {! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea

    = $(this).find('textarea');! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: $textarea.val() },! success: function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! }! });! });! }); 1. evento de página 2. evento do usuário 3. requisição de rede 4. entrada de dados 5. evento de rede 6. HTML template
  6. // app.js! $(document).ready(function() {! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea

    = $(this).find('textarea');! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: $textarea.val() },! success: function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! }! });! });! });
  7. // app.js! function addComment(comment, callback) {! $.ajax({! url: '/comments',! type:

    'POST',! dataType: 'json',! data: { text: comment },! success: callback! });! };! ! $(document).ready(function() {! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! …! });! });
  8. // app.js! function addComment(comment, callback) {! $.ajax({! url: '/comments',! type:

    'POST',! dataType: 'json',! data: { text: comment },! success: callback! });! };! ! $(document).ready(function() {! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! addComment($textarea.val(), function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! });! });! });
  9. // app.js! var Comments = function() {};! function addComment(comment, callback)

    {! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: comment },! success: callback! });! };! ! $(document).ready(function() {! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! addComment($textarea.val(), function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! });! });! });
  10. // app.js! var Comments = function() {};! Comments.prototype.add = function(comment,

    callback) {! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: comment },! success: callback! });! };! ! $(document).ready(function() {! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! addComment($textarea.val(), function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! });! });! });
  11. // app.js! var Comments = function() {};! Comments.prototype.add = function(comment,

    callback) {! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: comment },! success: callback! });! };! ! $(document).ready(function() {! var comments = new Comments();! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! comments.add($textarea.val(), function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! });! });! });
  12. // app.js! var Comments = function() {};! Comments.prototype.add = function(comment,

    callback) {! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: comment },! success: callback! });! };! ! $(document).ready(function() {! var comments = new Comments();! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! comments.add($textarea.val(), function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! });! });! });!
  13. // app.js! var Comments = function() {};! BackboneEvents.mixin(Comments.prototype);! Comments.prototype.add =

    function(comment, callback) {! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: comment },! success: callback! });! };! ! $(document).ready(function() {! var comments = new Comments();! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! comments.add($textarea.val(), function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! });! });! });!
  14. // app.js! var Comments = function() {};! BackboneEvents.mixin(Comments.prototype);! Comments.prototype.add =

    function(comment) {! var _this = this;! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: comment },! success: function(data){ _this.trigger('add', data.text);}! });! };! ! $(document).ready(function() {! var comments = new Comments();! $('#comment-form').on('submit', function(e) {! e.preventDefault();! var $textarea = $(this).find('textarea');! comments.add($textarea.val(), function(data) {! $('#comment-list').append('<li>' + data.text + '</li>');! $textarea.val('');! });! });! });!
  15. // app.js! var Comments = function() {};! BackboneEvents.mixin(Comments.prototype);! Comments.prototype.add =

    function(comment) {! var _this = this;! $.ajax({! url: '/comments',! type: 'POST',! dataType: 'json',! data: { text: comment },! success: function(data){ _this.trigger('add', data.text);}! });! };! ! $(document).ready(function() {! var comments = new Comments();! comments.on("add", function(text) {! $('#comment-list').append('<li>' + text + '</li>');! $('#comment-form').find("textarea").val('');! });! $('#comment-form').on('submit', function(e) {! e.preventDefault();! comments.add($(this).find('textarea').val());! });! });!
  16. // spec/CommentsSpec.js! describe("Comments", function() {! var comments;! ! beforeEach(function() {!

    comments = new Comments();! });! ! describe("adding a new comment", function() {! it("triggers the add event", function() {! comments.add("new comment");! });! });! });
  17. // spec/CommentsSpec.js! describe("Comments", function() {! var comments, addComment;! ! beforeEach(function()

    {! comments = new Comments();! addComment = jasmine.createSpy();! comments.on("add", addComment);! });! ! describe("adding a new comment", function() {! it("triggers the add event", function() {! comments.add("new comment");! expect(addComment).toHaveBeenCalledWith("new comment");! });! });! });
  18. // spec/CommentsSpec.js! describe("Comments", function() {! var comments, addComment;! ! beforeEach(function()

    {! comments = new Comments();! addComment = jasmine.createSpy();! comments.on("add", addComment);! });! ! describe("adding a new comment", function() {! it("triggers the add event", function() {! comments.add("new comment");! expect(addComment).toHaveBeenCalledWith("new comment");! });! });! });
  19. // spec/CommentsSpec.js! describe("Comments", function() {! var comments, addComment;! ! beforeEach(function()

    {! spyOn($, "ajax").and.callFake(function(options) {! options.success({text: options.data.text});! });! ! comments = new Comments();! addComment = jasmine.createSpy();! comments.on("add", addComment);! });! ! describe("adding a new comment", function() {! it("triggers the add event", function() {! comments.add("new comment");! expect(addComment).toHaveBeenCalledWith("new comment");! });! });! });
  20. View para o Form var CommentFormView = function(options) {! this.$el

    = options.el;! this.$textarea = this.$el.find('textarea');! };
  21. View para o Form var CommentFormView = function(options) {! this.$el

    = options.el;! this.$textarea = this.$el.find('textarea');! this.$el.on("submit", this.addComment);! };! CommentFormView.prototype.addComment = function(e) {! e.preventDefault();! };
  22. View para o Form var CommentFormView = function(options) {! this.$el

    = options.el;! this.$textarea = this.$el.find('textarea');! this.comments = options.comments;! this.$el.on("submit", this.addComment);! };! CommentFormView.prototype.addComment = function(e) {! e.preventDefault();! this.comments.add(this.$textarea.val());! };
  23. View para o Form var CommentFormView = function(options) {! this.$el

    = options.el;! this.$textarea = this.$el.find('textarea');! this.comments = options.comments;! this.$el.on("submit", $.proxy(this.addComment, this));! };! CommentFormView.prototype.addComment = function(e) {! e.preventDefault();! this.comments.add(this.$textarea.val());! };
  24. View para o Form var CommentFormView = function(options) {! this.$el

    = options.el;! this.$textarea = this.$el.find('textarea');! this.comments = options.comments;! this.comments.on('add', this.clearInput, this);! this.$el.on("submit", $.proxy(this.addComment, this));! };! CommentFormView.prototype.addComment = function(e) {! e.preventDefault();! this.comments.add(this.$textarea.val());! };! CommentFormView.prototype.clearInput = function() {! this.$textarea.val('');! };!
  25. View para Lista de Comentários var CommentListView = function(options) {!

    this.$el = options.el;! this.comments = options.comments;! this.comments.on('add', this.appendComment, this);! };! CommentListView.prototype.appendComment = function(text) {! this.$el.append('<li>' + text + '</li>');! };!
  26. $(document).ready(function() {! var comments = new Comments();! ! new CommentFormView({

    el: $('#comment-form'), comments: comments });! new CommentListView({ el: $('#comment-list'), comments: comments });! }); Injeção de dependências
  27. // spec/CommentFormView.js! describe("CommentFormView", function() {! var $el, comments, view;! !

    beforeEach(function() {! $el = $("<form><textarea>My Comment</textarea></form>");! comments = new Comments();! view = new CommentFormView({el: $el, comments: comments});! });! });
  28. // spec/CommentFormView.js! describe("CommentFormView", function() {! var $el, comments, view;! !

    beforeEach(function() {! $el = $("<form><textarea>My Comment</textarea></form>");! comments = {add: jasmine.createSpy("add")};! BackboneEvents.mixin(comments);! view = new CommentFormView({el: $el, comments: comments});! });! });
  29. // spec/CommentFormView.js! describe("CommentFormView", function() {! var $el, comments, view;! !

    beforeEach(function() {! $el = $("<form><textarea>My Comment</textarea></form>");! comments = {add: jasmine.createSpy("add")};! BackboneEvents.mixin(comments);! view = new CommentFormView({el: $el, comments: comments});! });! ! describe("submitting the form", function() {! it("creates a comment", function() {! $el.trigger("submit");! expect(comments.add).toHaveBeenCalledWith("My Comment");! });! });! });
  30. // spec/CommentFormView.js! describe("CommentFormView", function() {! var $el, comments, view;! !

    beforeEach(function() {! $el = $("<form><textarea>My Comment</textarea></form>");! comments = {add: jasmine.createSpy("add")};! BackboneEvents.mixin(comments);! view = new CommentFormView({el: $el, comments: comments});! });! ! describe("submitting the form", function() {! it("creates a comment", function() {! $el.trigger("submit");! expect(comments.add).toHaveBeenCalledWith("My Comment");! });! });! ! describe("adding a new comment", function() {! it("clears the input", function() {! comments.trigger("add");! expect($el.find("textarea").val()).toEqual("");! });! });! });
  31. Unit Tests Lista de Comentários describe("CommentListView", function() {! var $el,

    comments, view;! ! beforeEach(function() {! $el = $("<ul></ul>");! comments = new Comments();! view = new CommentListView({el: $el, comments: comments});! });! });
  32. Unit Tests Lista de Comentários describe("CommentListView", function() {! var $el,

    comments, view;! ! beforeEach(function() {! $el = $("<ul></ul>");! comments = {};! BackboneEvents.mixin(comments);! view = new CommentListView({el: $el, comments: comments});! });! });
  33. Unit Tests Lista de Comentários describe("CommentListView", function() {! var $el,

    comments, view;! ! beforeEach(function() {! $el = $("<ul></ul>");! comments = {};! BackboneEvents.mixin(comments);! view = new CommentListView({el: $el, comments: comments});! });! ! describe("adding a new comment", function() {! it("appends a new list item", function() {! comments.trigger("add", "new comment"); ! });! });! });
  34. Unit Tests Lista de Comentários describe("CommentListView", function() {! var $el,

    comments, view;! ! beforeEach(function() {! $el = $("<ul></ul>");! comments = {};! BackboneEvents.mixin(comments);! view = new CommentListView({el: $el, comments: comments});! });! ! describe("adding a new comment", function() {! it("appends a new list item", function() {! comments.trigger("add", "new comment");! expect($el.find("li:last").html()).toEqual("new comment");! });! });! });
  35. Próximos passos Module Pattern (CommonJS, AMD, ES6) Modules Loader (Browserfy,

    Require.js, …) Templates Engine (EJS, Jade, Handlebars) MV** Framework (Backbone.js, Angular.js, Ember.js, React.js + Flux) Automatização (Grunt, Gulp, Sprockets)