From 83c9d8cf86f3c9b35a70e3e9c4bef81bb4fc41b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=92=83=20Winnie=20=F0=9F=92=83?=
 <gitlab@winniehell.de>
Date: Wed, 12 Apr 2017 19:40:35 +0000
Subject: [PATCH] Reset New branch button when issue state changes

---
 app/assets/javascripts/issue.js               | 108 +++++-----
 .../unreleased/reset-new-branch-button.yml    |   4 +
 spec/javascripts/issue_spec.js                | 194 ++++++++++--------
 3 files changed, 170 insertions(+), 136 deletions(-)
 create mode 100644 changelogs/unreleased/reset-new-branch-button.yml

diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 47e675f537e..011043e992f 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -20,57 +20,60 @@ class Issue {
       });
       Issue.initIssueBtnEventListeners();
     }
+
+    Issue.$btnNewBranch = $('#new-branch');
+
     Issue.initMergeRequests();
     Issue.initRelatedBranches();
     Issue.initCanCreateBranch();
   }
 
   static initIssueBtnEventListeners() {
-    var issueFailMessage;
-    issueFailMessage = 'Unable to update this issue at this time.';
-    return $('a.btn-close, a.btn-reopen').on('click', function(e) {
-      var $this, isClose, shouldSubmit, url;
+    const issueFailMessage = 'Unable to update this issue at this time.';
+
+    const closeButtons = $('a.btn-close');
+    const isClosedBadge = $('div.status-box-closed');
+    const isOpenBadge = $('div.status-box-open');
+    const projectIssuesCounter = $('.issue_counter');
+    const reopenButtons = $('a.btn-reopen');
+
+    return closeButtons.add(reopenButtons).on('click', function(e) {
+      var $this, shouldSubmit, url;
       e.preventDefault();
       e.stopImmediatePropagation();
       $this = $(this);
-      isClose = $this.hasClass('btn-close');
       shouldSubmit = $this.hasClass('btn-comment');
       if (shouldSubmit) {
         Issue.submitNoteForm($this.closest('form'));
       }
       $this.prop('disabled', true);
+      Issue.setNewBranchButtonState(true, null);
       url = $this.attr('href');
       return $.ajax({
         type: 'PUT',
-        url: url,
-        error: function(jqXHR, textStatus, errorThrown) {
-          var issueStatus;
-          issueStatus = isClose ? 'close' : 'open';
-          return new Flash(issueFailMessage, 'alert');
-        },
-        success: function(data, textStatus, jqXHR) {
-          if ('id' in data) {
-            $(document).trigger('issuable:change');
-            let total = Number($('.issue_counter').text().replace(/[^\d]/, ''));
-            if (isClose) {
-              $('a.btn-close').addClass('hidden');
-              $('a.btn-reopen').removeClass('hidden');
-              $('div.status-box-closed').removeClass('hidden');
-              $('div.status-box-open').addClass('hidden');
-              total -= 1;
-            } else {
-              $('a.btn-reopen').addClass('hidden');
-              $('a.btn-close').removeClass('hidden');
-              $('div.status-box-closed').addClass('hidden');
-              $('div.status-box-open').removeClass('hidden');
-              total += 1;
-            }
-            $('.issue_counter').text(gl.text.addDelimiter(total));
-          } else {
-            new Flash(issueFailMessage, 'alert');
-          }
-          return $this.prop('disabled', false);
+        url: url
+      }).fail(function(jqXHR, textStatus, errorThrown) {
+        new Flash(issueFailMessage);
+        Issue.initCanCreateBranch();
+      }).done(function(data, textStatus, jqXHR) {
+        if ('id' in data) {
+          $(document).trigger('issuable:change');
+
+          const isClosed = $this.hasClass('btn-close');
+          closeButtons.toggleClass('hidden', isClosed);
+          reopenButtons.toggleClass('hidden', !isClosed);
+          isClosedBadge.toggleClass('hidden', !isClosed);
+          isOpenBadge.toggleClass('hidden', isClosed);
+
+          let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
+          numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
+          projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
+        } else {
+          new Flash(issueFailMessage);
         }
+
+        $this.prop('disabled', false);
+        Issue.initCanCreateBranch();
       });
     });
   }
@@ -86,9 +89,9 @@ class Issue {
   static initMergeRequests() {
     var $container;
     $container = $('#merge-requests');
-    return $.getJSON($container.data('url')).error(function() {
-      return new Flash('Failed to load referenced merge requests', 'alert');
-    }).success(function(data) {
+    return $.getJSON($container.data('url')).fail(function() {
+      return new Flash('Failed to load referenced merge requests');
+    }).done(function(data) {
       if ('html' in data) {
         return $container.html(data.html);
       }
@@ -98,9 +101,9 @@ class Issue {
   static initRelatedBranches() {
     var $container;
     $container = $('#related-branches');
-    return $.getJSON($container.data('url')).error(function() {
-      return new Flash('Failed to load related branches', 'alert');
-    }).success(function(data) {
+    return $.getJSON($container.data('url')).fail(function() {
+      return new Flash('Failed to load related branches');
+    }).done(function(data) {
       if ('html' in data) {
         return $container.html(data.html);
       }
@@ -108,24 +111,27 @@ class Issue {
   }
 
   static initCanCreateBranch() {
-    var $container;
-    $container = $('#new-branch');
     // If the user doesn't have the required permissions the container isn't
     // rendered at all.
-    if ($container.length === 0) {
+    if (Issue.$btnNewBranch.length === 0) {
       return;
     }
-    return $.getJSON($container.data('path')).error(function() {
-      $container.find('.unavailable').show();
-      return new Flash('Failed to check if a new branch can be created.', 'alert');
-    }).success(function(data) {
-      if (data.can_create_branch) {
-        $container.find('.available').show();
-      } else {
-        return $container.find('.unavailable').show();
-      }
+    return $.getJSON(Issue.$btnNewBranch.data('path')).fail(function() {
+      Issue.setNewBranchButtonState(false, false);
+      new Flash('Failed to check if a new branch can be created.');
+    }).done(function(data) {
+      Issue.setNewBranchButtonState(false, data.can_create_branch);
     });
   }
+
+  static setNewBranchButtonState(isPending, canCreate) {
+    if (Issue.$btnNewBranch.length === 0) {
+      return;
+    }
+
+    Issue.$btnNewBranch.find('.available').toggle(!isPending && canCreate);
+    Issue.$btnNewBranch.find('.unavailable').toggle(!isPending && !canCreate);
+  }
 }
 
 export default Issue;
diff --git a/changelogs/unreleased/reset-new-branch-button.yml b/changelogs/unreleased/reset-new-branch-button.yml
new file mode 100644
index 00000000000..318ee46298f
--- /dev/null
+++ b/changelogs/unreleased/reset-new-branch-button.yml
@@ -0,0 +1,4 @@
+---
+title: Reset New branch button when issue state changes
+merge_request: 5962
+author: winniehell
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index aabc8bea12f..9a2570ef7e9 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,18 +1,17 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
+/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
 import Issue from '~/issue';
 
 require('~/lib/utils/text_utility');
 
 describe('Issue', function() {
-  var INVALID_URL = 'http://goesnowhere.nothing/whereami';
-  var $boxClosed, $boxOpen, $btnClose, $btnReopen;
+  let $boxClosed, $boxOpen, $btnClose, $btnReopen;
 
   preloadFixtures('issues/closed-issue.html.raw');
   preloadFixtures('issues/issue-with-task-list.html.raw');
   preloadFixtures('issues/open-issue.html.raw');
 
   function expectErrorMessage() {
-    var $flashMessage = $('div.flash-alert');
+    const $flashMessage = $('div.flash-alert');
     expect($flashMessage).toExist();
     expect($flashMessage).toBeVisible();
     expect($flashMessage).toHaveText('Unable to update this issue at this time.');
@@ -26,10 +25,28 @@ describe('Issue', function() {
     expectVisibility($btnReopen, !isIssueOpen);
   }
 
-  function expectPendingRequest(req, $triggeredButton) {
-    expect(req.type).toBe('PUT');
-    expect(req.url).toBe($triggeredButton.attr('href'));
-    expect($triggeredButton).toHaveProp('disabled', true);
+  function expectNewBranchButtonState(isPending, canCreate) {
+    if (Issue.$btnNewBranch.length === 0) {
+      return;
+    }
+
+    const $available = Issue.$btnNewBranch.find('.available');
+    expect($available).toHaveText('New branch');
+
+    if (!isPending && canCreate) {
+      expect($available).toBeVisible();
+    } else {
+      expect($available).toBeHidden();
+    }
+
+    const $unavailable = Issue.$btnNewBranch.find('.unavailable');
+    expect($unavailable).toHaveText('New branch unavailable');
+
+    if (!isPending && !canCreate) {
+      expect($unavailable).toBeVisible();
+    } else {
+      expect($unavailable).toBeHidden();
+    }
   }
 
   function expectVisibility($element, shouldBeVisible) {
@@ -81,100 +98,107 @@ describe('Issue', function() {
     });
   });
 
-  describe('close issue', function() {
-    beforeEach(function() {
-      loadFixtures('issues/open-issue.html.raw');
-      findElements();
-      this.issue = new Issue();
-
-      expectIssueState(true);
-    });
+  [true, false].forEach((isIssueInitiallyOpen) => {
+    describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
+      const action = isIssueInitiallyOpen ? 'close' : 'reopen';
+
+      function ajaxSpy(req) {
+        if (req.url === this.$triggeredButton.attr('href')) {
+          expect(req.type).toBe('PUT');
+          expect(this.$triggeredButton).toHaveProp('disabled', true);
+          expectNewBranchButtonState(true, false);
+          return this.issueStateDeferred;
+        } else if (req.url === Issue.$btnNewBranch.data('path')) {
+          expect(req.type).toBe('get');
+          expectNewBranchButtonState(true, false);
+          return this.canCreateBranchDeferred;
+        }
+
+        expect(req.url).toBe('unexpected');
+        return null;
+      }
+
+      beforeEach(function() {
+        if (isIssueInitiallyOpen) {
+          loadFixtures('issues/open-issue.html.raw');
+        } else {
+          loadFixtures('issues/closed-issue.html.raw');
+        }
+
+        findElements();
+        this.issue = new Issue();
+        expectIssueState(isIssueInitiallyOpen);
+        this.$triggeredButton = isIssueInitiallyOpen ? $btnClose : $btnReopen;
+
+        this.$projectIssuesCounter = $('.issue_counter');
+        this.$projectIssuesCounter.text('1,001');
+
+        this.issueStateDeferred = new jQuery.Deferred();
+        this.canCreateBranchDeferred = new jQuery.Deferred();
+
+        spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this));
+      });
 
-    it('closes an issue', function() {
-      spyOn(jQuery, 'ajax').and.callFake(function(req) {
-        expectPendingRequest(req, $btnClose);
-        req.success({
+      it(`${action}s the issue`, function() {
+        this.$triggeredButton.trigger('click');
+        this.issueStateDeferred.resolve({
           id: 34
         });
-      });
-
-      $btnClose.trigger('click');
+        this.canCreateBranchDeferred.resolve({
+          can_create_branch: !isIssueInitiallyOpen
+        });
 
-      expectIssueState(false);
-      expect($btnClose).toHaveProp('disabled', false);
-      expect($('.issue_counter')).toHaveText(0);
-    });
+        expectIssueState(!isIssueInitiallyOpen);
+        expect(this.$triggeredButton).toHaveProp('disabled', false);
+        expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
+        expectNewBranchButtonState(false, !isIssueInitiallyOpen);
+      });
 
-    it('fails to close an issue with success:false', function() {
-      spyOn(jQuery, 'ajax').and.callFake(function(req) {
-        expectPendingRequest(req, $btnClose);
-        req.success({
+      it(`fails to ${action} the issue if saved:false`, function() {
+        this.$triggeredButton.trigger('click');
+        this.issueStateDeferred.resolve({
           saved: false
         });
-      });
-
-      $btnClose.attr('href', INVALID_URL);
-      $btnClose.trigger('click');
-
-      expectIssueState(true);
-      expect($btnClose).toHaveProp('disabled', false);
-      expectErrorMessage();
-      expect($('.issue_counter')).toHaveText(1);
-    });
+        this.canCreateBranchDeferred.resolve({
+          can_create_branch: isIssueInitiallyOpen
+        });
 
-    it('fails to closes an issue with HTTP error', function() {
-      spyOn(jQuery, 'ajax').and.callFake(function(req) {
-        expectPendingRequest(req, $btnClose);
-        req.error();
+        expectIssueState(isIssueInitiallyOpen);
+        expect(this.$triggeredButton).toHaveProp('disabled', false);
+        expectErrorMessage();
+        expect(this.$projectIssuesCounter.text()).toBe('1,001');
+        expectNewBranchButtonState(false, isIssueInitiallyOpen);
       });
 
-      $btnClose.attr('href', INVALID_URL);
-      $btnClose.trigger('click');
-
-      expectIssueState(true);
-      expect($btnClose).toHaveProp('disabled', true);
-      expectErrorMessage();
-      expect($('.issue_counter')).toHaveText(1);
-    });
-
-    it('updates counter', () => {
-      spyOn(jQuery, 'ajax').and.callFake(function(req) {
-        expectPendingRequest(req, $btnClose);
-        req.success({
-          id: 34
+      it(`fails to ${action} the issue if HTTP error occurs`, function() {
+        this.$triggeredButton.trigger('click');
+        this.issueStateDeferred.reject();
+        this.canCreateBranchDeferred.resolve({
+          can_create_branch: isIssueInitiallyOpen
         });
-      });
 
-      expect($('.issue_counter')).toHaveText(1);
-      $('.issue_counter').text('1,001');
-      expect($('.issue_counter').text()).toEqual('1,001');
-      $btnClose.trigger('click');
-      expect($('.issue_counter').text()).toEqual('1,000');
-    });
-  });
+        expectIssueState(isIssueInitiallyOpen);
+        expect(this.$triggeredButton).toHaveProp('disabled', true);
+        expectErrorMessage();
+        expect(this.$projectIssuesCounter.text()).toBe('1,001');
+        expectNewBranchButtonState(false, isIssueInitiallyOpen);
+      });
 
-  describe('reopen issue', function() {
-    beforeEach(function() {
-      loadFixtures('issues/closed-issue.html.raw');
-      findElements();
-      this.issue = new Issue();
+      it('disables the new branch button if Ajax call fails', function() {
+        this.$triggeredButton.trigger('click');
+        this.issueStateDeferred.reject();
+        this.canCreateBranchDeferred.reject();
 
-      expectIssueState(false);
-    });
-
-    it('reopens an issue', function() {
-      spyOn(jQuery, 'ajax').and.callFake(function(req) {
-        expectPendingRequest(req, $btnReopen);
-        req.success({
-          id: 34
-        });
+        expectNewBranchButtonState(false, false);
       });
 
-      $btnReopen.trigger('click');
+      it('does not trigger Ajax call if new branch button is missing', function() {
+        Issue.$btnNewBranch = $();
+        this.canCreateBranchDeferred = null;
 
-      expectIssueState(true);
-      expect($btnReopen).toHaveProp('disabled', false);
-      expect($('.issue_counter')).toHaveText(1);
+        this.$triggeredButton.trigger('click');
+        this.issueStateDeferred.reject();
+      });
     });
   });
 });
-- 
GitLab