diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index 1638e09132b4cd5b35a7ff848ccd2eec4ede78a7..b0c85c2572e0dfdfae7bec8604e6e3a0fd0ba70f 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -2,13 +2,16 @@ import $ from 'jquery';
 import { rstrip } from './lib/utils/common_utils';
 
 function openConfirmDangerModal($form, text) {
+  const $input = $('.js-confirm-danger-input');
+  $input.val('');
+
   $('.js-confirm-text').text(text || '');
-  $('.js-confirm-danger-input').val('');
   $('#modal-confirm-danger').modal('show');
 
   const confirmTextMatch = $('.js-confirm-danger-match').text();
   const $submit = $('.js-confirm-danger-submit');
   $submit.disable();
+  $input.focus();
 
   $('.js-confirm-danger-input').off('input').on('input', function handleInput() {
     const confirmText = rstrip($(this).val());
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 060386c3ecbd5962ecf42045b0cf38e1676d0468..a61e368249a5ec18411002b3eb00b583bd3df694 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -31,9 +31,6 @@ export default {
     };
   },
   computed: {
-    isDiscussionsExpanded() {
-      return true; // TODO: @fatihacet - Fix this.
-    },
     isCollapsed() {
       return this.file.collapsed || false;
     },
@@ -131,7 +128,6 @@ export default {
       :diff-file="file"
       :collapsible="true"
       :expanded="!isCollapsed"
-      :discussions-expanded="isDiscussionsExpanded"
       :add-merge-request-buttons="true"
       class="js-file-title file-title"
       @toggleFile="handleToggle"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 1957698c6c10bae664ea51568a295317683ea0ae..c5abd0a9568c03397449d546e59c5d619d5e04d6 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -1,5 +1,6 @@
 <script>
 import _ from 'underscore';
+import { mapActions, mapGetters } from 'vuex';
 import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
 import Icon from '~/vue_shared/components/icon.vue';
 import FileIcon from '~/vue_shared/components/file_icon.vue';
@@ -38,11 +39,6 @@ export default {
       required: false,
       default: true,
     },
-    discussionsExpanded: {
-      type: Boolean,
-      required: false,
-      default: true,
-    },
     currentUser: {
       type: Object,
       required: true,
@@ -54,6 +50,10 @@ export default {
     };
   },
   computed: {
+    ...mapGetters('diffs', ['diffHasExpandedDiscussions']),
+    hasExpandedDiscussions() {
+      return this.diffHasExpandedDiscussions(this.diffFile);
+    },
     icon() {
       if (this.diffFile.submodule) {
         return 'archive';
@@ -88,9 +88,6 @@ export default {
     collapseIcon() {
       return this.expanded ? 'chevron-down' : 'chevron-right';
     },
-    isDiscussionsExpanded() {
-      return this.discussionsExpanded && this.expanded;
-    },
     viewFileButtonText() {
       const truncatedContentSha = _.escape(truncateSha(this.diffFile.contentSha));
       return sprintf(
@@ -113,7 +110,8 @@ export default {
     },
   },
   methods: {
-    handleToggle(e, checkTarget) {
+    ...mapActions('diffs', ['toggleFileDiscussions']),
+    handleToggleFile(e, checkTarget) {
       if (
         !checkTarget ||
         e.target === this.$refs.header ||
@@ -125,6 +123,9 @@ export default {
     showForkMessage() {
       this.$emit('showForkMessage');
     },
+    handleToggleDiscussions() {
+      this.toggleFileDiscussions(this.diffFile);
+    },
   },
 };
 </script>
@@ -133,7 +134,7 @@ export default {
   <div
     ref="header"
     class="js-file-title file-title file-title-flex-parent"
-    @click="handleToggle($event, true)"
+    @click="handleToggleFile($event, true)"
   >
     <div class="file-header-content">
       <icon
@@ -216,10 +217,11 @@ export default {
         v-if="diffFile.blob && diffFile.blob.readableText"
       >
         <button
-          :class="{ active: isDiscussionsExpanded }"
+          :class="{ active: hasExpandedDiscussions }"
           :title="s__('MergeRequests|Toggle comments for this file')"
-          class="btn js-toggle-diff-comments"
+          class="js-btn-vue-toggle-comments btn"
           type="button"
+          @click="handleToggleDiscussions"
         >
           <icon name="comment" />
         </button>
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 5bfe42618c28b4a755ec797cdc028a7fc84fb9f8..27001142257f7957f2c90fe40904d6987084da91 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -82,5 +82,32 @@ export const expandAllFiles = ({ commit }) => {
   commit(types.EXPAND_ALL_FILES);
 };
 
+/**
+ * Toggles the file discussions after user clicked on the toggle discussions button.
+ *
+ * Gets the discussions for the provided diff.
+ *
+ * If all discussions are expanded, it will collapse all of them
+ * If all discussions are collapsed, it will expand all of them
+ * If some discussions are open and others closed, it will expand the closed ones.
+ *
+ * @param {Object} diff
+ */
+export const toggleFileDiscussions = ({ getters, dispatch }, diff) => {
+  const discussions = getters.getDiffFileDiscussions(diff);
+  const shouldCloseAll = getters.diffHasAllExpandedDiscussions(diff);
+  const shouldExpandAll = getters.diffHasAllCollpasedDiscussions(diff);
+
+  discussions.forEach(discussion => {
+    const data = { discussionId: discussion.id };
+
+    if (shouldCloseAll) {
+      dispatch('collapseDiscussion', data, { root: true });
+    } else if (shouldExpandAll || (!shouldCloseAll && !shouldExpandAll && !discussion.expanded)) {
+      dispatch('expandDiscussion', data, { root: true });
+    }
+  });
+};
+
 // prevent babel-plugin-rewire from generating an invalid default during karma tests
 export default () => {};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index f3c2d7427e7c0fcfd09369a2f1c3f418ebf0a774..f89acb73ed8be2a239bf91d3c60aed95e009b572 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
 import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants';
 
 export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
@@ -8,5 +9,52 @@ export const areAllFilesCollapsed = state => state.diffFiles.every(file => file.
 
 export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
 
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
+/**
+ * Checks if the diff has all discussions expanded
+ * @param {Object} diff
+ * @returns {Boolean}
+ */
+export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
+  const discussions = getters.getDiffFileDiscussions(diff);
+
+  return (discussions.length && discussions.every(discussion => discussion.expanded)) || false;
+};
+
+/**
+ * Checks if the diff has all discussions collpased
+ * @param {Object} diff
+ * @returns {Boolean}
+ */
+export const diffHasAllCollpasedDiscussions = (state, getters) => diff => {
+  const discussions = getters.getDiffFileDiscussions(diff);
+
+  return (discussions.length && discussions.every(discussion => !discussion.expanded)) || false;
+};
+
+/**
+ * Checks if the diff has any open discussions
+ * @param {Object} diff
+ * @returns {Boolean}
+ */
+export const diffHasExpandedDiscussions = (state, getters) => diff => {
+  const discussions = getters.getDiffFileDiscussions(diff);
+
+  return (
+    (discussions.length && discussions.find(discussion => discussion.expanded) !== undefined) ||
+    false
+  );
+};
+
+/**
+ * Returns an array with the discussions of the given diff
+ * @param {Object} diff
+ * @returns {Array}
+ */
+export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) => diff =>
+  rootGetters.discussions.filter(
+    discussion =>
+      discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
+  ) || [];
+
+// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
 export default () => {};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index b2bf86eea56810ac9bffd89a4555fe11ebe4c6ce..3eefbe11c37c09e8fa276c235d1d002c7d5ff6ce 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -15,6 +15,8 @@ let eTagPoll;
 
 export const expandDiscussion = ({ commit }, data) => commit(types.EXPAND_DISCUSSION, data);
 
+export const collapseDiscussion = ({ commit }, data) => commit(types.COLLAPSE_DISCUSSION, data);
+
 export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
 
 export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index a25098fbc06773387b9ee7c96980d1342d3a5ab2..6f374f78691f0e4ff8b1c625d1cc158b834b13aa 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -1,7 +1,6 @@
 export const ADD_NEW_NOTE = 'ADD_NEW_NOTE';
 export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION';
 export const DELETE_NOTE = 'DELETE_NOTE';
-export const EXPAND_DISCUSSION = 'EXPAND_DISCUSSION';
 export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES';
 export const SET_NOTES_DATA = 'SET_NOTES_DATA';
 export const SET_NOTEABLE_DATA = 'SET_NOTEABLE_DATA';
@@ -11,12 +10,16 @@ export const SET_LAST_FETCHED_AT = 'SET_LAST_FETCHED_AT';
 export const SET_TARGET_NOTE_HASH = 'SET_TARGET_NOTE_HASH';
 export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE';
 export const TOGGLE_AWARD = 'TOGGLE_AWARD';
-export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION';
 export const UPDATE_NOTE = 'UPDATE_NOTE';
 export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
 export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
 export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
 
+// DISCUSSION
+export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
+export const EXPAND_DISCUSSION = 'EXPAND_DISCUSSION';
+export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION';
+
 // Issue
 export const CLOSE_ISSUE = 'CLOSE_ISSUE';
 export const REOPEN_ISSUE = 'REOPEN_ISSUE';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index a184926901091ad81e04d83929e4f5cb7a60df46..ab6a95e2601bb945dca326598b6f421f9e938310 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -58,6 +58,11 @@ export default {
     discussion.expanded = true;
   },
 
+  [types.COLLAPSE_DISCUSSION](state, { discussionId }) {
+    const discussion = utils.findNoteObjectById(state.discussions, discussionId);
+    discussion.expanded = false;
+  },
+
   [types.REMOVE_PLACEHOLDER_NOTES](state) {
     const { discussions } = state;
 
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index c32049e1b338fa5aafc29ff31c05c9548e79af34..2af79c511ba0909e3a8540647e2fd79ea6c80107 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -116,10 +116,8 @@
 
       .modify-merge-commit-link {
         padding: 0;
-
         background-color: transparent;
         border: 0;
-
         color: $gl-text-color;
 
         &:hover,
@@ -501,10 +499,6 @@
   }
 }
 
-.merge-request-details .content-block {
-  border-bottom: 0;
-}
-
 .mr-source-target {
   display: flex;
   flex-wrap: wrap;
diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml
index 8a390cf870044973f247fd280a35d0ef4333c04c..1a9ab288683deab8a19e8d7573c5c300e69d064c 100644
--- a/app/views/projects/merge_requests/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/_mr_box.html.haml
@@ -1,4 +1,4 @@
-.detail-page-description.content-block
+.detail-page-description
   %h2.title
     = markdown_field(@merge_request, :title)
 
diff --git a/bin/changelog b/bin/changelog
index d7b2a1a2de9b524601c3fd2079383c238dd7f31d..758c036161e6d5d978f850fb7cc371a1973a81f4 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -23,6 +23,8 @@ module ChangelogHelpers
   Abort = Class.new(StandardError)
   Done = Class.new(StandardError)
 
+  MAX_FILENAME_LENGTH = 140 # ecryptfs has a limit of 140 characters
+
   def capture_stdout(cmd)
     output = IO.popen(cmd, &:read)
     fail_with "command failed: #{cmd.join(' ')}" unless $?.success?
@@ -142,7 +144,9 @@ class ChangelogEntry
 
   def initialize(options)
     @options = options
+  end
 
+  def execute
     assert_feature_branch!
     assert_title!
     assert_new_file!
@@ -221,10 +225,12 @@ class ChangelogEntry
   end
 
   def file_path
-    File.join(
+    base_path = File.join(
       unreleased_path,
-      branch_name.gsub(/[^\w-]/, '-') << '.yml'
-    )
+      branch_name.gsub(/[^\w-]/, '-'))
+
+    # Add padding for .yml extension
+    base_path[0..MAX_FILENAME_LENGTH - 5] + '.yml'
   end
 
   def unreleased_path
@@ -250,7 +256,7 @@ end
 if $0 == __FILE__
   begin
     options = ChangelogOptionParser.parse(ARGV)
-    ChangelogEntry.new(options)
+    ChangelogEntry.new(options).execute
   rescue ChangelogHelpers::Abort => ex
     $stderr.puts ex.message
     exit 1
diff --git a/changelogs/unreleased/48237-toggle-file-comments.yml b/changelogs/unreleased/48237-toggle-file-comments.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2e893aad0b21dd750703bc8a7d881586c78f5951
--- /dev/null
+++ b/changelogs/unreleased/48237-toggle-file-comments.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes toggle discussion button not expanding collapsed discussions
+merge_request: 20452
+author:
+type: fixed
diff --git a/changelogs/unreleased/48934.yml b/changelogs/unreleased/48934.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e2e53ed198e314592fc86ca6bd8c939e7889199
--- /dev/null
+++ b/changelogs/unreleased/48934.yml
@@ -0,0 +1,5 @@
+---
+title: Improve danger confirmation modals by focusing input field
+merge_request:
+author: Jamie Schembri
+type: added
diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb
index da43bd0af4b567ea46c15b37836ec73d9a2d1f56..15c5ece235009fa4cb7c71e3195d6e50aa1ddf72 100644
--- a/lib/gitlab/kubernetes.rb
+++ b/lib/gitlab/kubernetes.rb
@@ -1,6 +1,10 @@
 module Gitlab
   # Helper methods to do with Kubernetes network services & resources
   module Kubernetes
+    def self.build_header_hash
+      Hash.new { |h, k| h[k] = [] }
+    end
+
     # This is the comand that is run to start a terminal session. Kubernetes
     # expects `command=foo&command=bar, not `command[]=foo&command[]=bar`
     EXEC_COMMAND = URI.encode_www_form(
@@ -37,13 +41,14 @@ module Gitlab
           selectors:    { pod: pod_name, container: container["name"] },
           url:          container_exec_url(api_url, namespace, pod_name, container["name"]),
           subprotocols: ['channel.k8s.io'],
-          headers:      Hash.new { |h, k| h[k] = [] },
+          headers:      ::Gitlab::Kubernetes.build_header_hash,
           created_at:   created_at
         }
       end
     end
 
     def add_terminal_auth(terminal, token:, max_session_time:, ca_pem: nil)
+      terminal[:headers] ||= ::Gitlab::Kubernetes.build_header_hash
       terminal[:headers]['Authorization'] << "Bearer #{token}"
       terminal[:max_session_time] = max_session_time
       terminal[:ca_pem] = ca_pem if ca_pem.present?
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index cee381f337987e9abb9e1bb8cb8d19ed3536266e..877864fb40c81285af084a313d6ece754101d5ad 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -85,6 +85,10 @@ module QA
           driver.browser.save_screenshot(path)
         end
 
+        Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example|
+          File.join(QA::Runtime::Namespace.name, example.file_path.sub('./qa/specs/features/', ''))
+        end
+
         Capybara.configure do |config|
           config.default_driver = :chrome
           config.javascript_driver = :chrome
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
index ccfa8b44db3f93bf8bcdfe3e62e1723858899392..28f17d1160b9a2836bb9f5419d794c5718772024 100644
--- a/qa/qa/runtime/namespace.rb
+++ b/qa/qa/runtime/namespace.rb
@@ -8,7 +8,7 @@ module QA
       end
 
       def name
-        'qa-test-' + time.strftime('%d-%m-%Y-%H-%M-%S')
+        "qa-test-#{time.strftime('%Y-%m-%d-%Y-%H-%M-%S')}"
       end
 
       def path
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index f278043028f09295081c606d184e62367c46de8b..9dc4edf97d1f7d34c3feb12b96754a923b13f424 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -3,6 +3,20 @@ require 'spec_helper'
 load File.expand_path('../../bin/changelog', __dir__)
 
 describe 'bin/changelog' do
+  let(:options) { OpenStruct.new(title: 'Test title', type: 'fixed', dry_run: true) }
+
+  describe ChangelogEntry do
+    it 'truncates the file path' do
+      entry = described_class.new(options)
+
+      allow(entry).to receive(:ee?).and_return(false)
+      allow(entry).to receive(:branch_name).and_return('long-branch-' * 100)
+
+      file_path = entry.send(:file_path)
+      expect(file_path.length).to eq(140)
+    end
+  end
+
   describe ChangelogOptionParser do
     describe '.parse' do
       it 'parses --amend' do
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 053e3b189c39bf812f9b203aa1c25e6217f8c11b..e62bd6f81873b2546b231149de3c87cfac56de47 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -154,6 +154,12 @@ describe 'Group' do
       end
     end
 
+    it 'focuses confirmation field on remove group' do
+      click_button('Remove group')
+
+      expect(page).to have_selector '#confirm_name_input:focus'
+    end
+
     it 'removes group' do
       expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1)
       expect(group.members.all.count).to be_zero
diff --git a/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb b/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb
index b1b62d04ac2f686b6b3952b464df4c137f5d70c6..441b080bee595fb764617c0b72a578c2880f4402 100644
--- a/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb
+++ b/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb
@@ -31,7 +31,7 @@ describe 'User comments on a diff', :js do
           page.within('.files > div:nth-child(3)') do
             expect(page).to have_content('Line is wrong')
 
-            find('.js-toggle-diff-comments').click
+            find('.js-btn-vue-toggle-comments').click
 
             expect(page).not_to have_content('Line is wrong')
           end
@@ -64,7 +64,7 @@ describe 'User comments on a diff', :js do
 
           # Hide the comment.
           page.within('.files > div:nth-child(3)') do
-            find('.js-toggle-diff-comments').click
+            find('.js-btn-vue-toggle-comments').click
 
             expect(page).not_to have_content('Line is wrong')
           end
@@ -77,7 +77,7 @@ describe 'User comments on a diff', :js do
 
           # Show the comment.
           page.within('.files > div:nth-child(3)') do
-            find('.js-toggle-diff-comments').click
+            find('.js-btn-vue-toggle-comments').click
           end
 
           # Now both the comments should be shown.
diff --git a/spec/features/projects/settings/user_transfers_a_project_spec.rb b/spec/features/projects/settings/user_transfers_a_project_spec.rb
index 96b7cf1f93b62996569353e7eabdf9e6db1b23be..2fdbc04fa62139565c15888ba6853328c256ce63 100644
--- a/spec/features/projects/settings/user_transfers_a_project_spec.rb
+++ b/spec/features/projects/settings/user_transfers_a_project_spec.rb
@@ -10,7 +10,7 @@ describe 'Projects > Settings > User transfers a project', :js do
     sign_in(user)
   end
 
-  def transfer_project(project, group)
+  def transfer_project(project, group, confirm: true)
     visit edit_project_path(project)
 
     page.within('.js-project-transfer-form') do
@@ -21,6 +21,8 @@ describe 'Projects > Settings > User transfers a project', :js do
 
     click_button('Transfer project')
 
+    return unless confirm
+
     fill_in 'confirm_name_input', with: project.name
 
     click_button 'Confirm'
@@ -28,6 +30,11 @@ describe 'Projects > Settings > User transfers a project', :js do
     wait_for_requests
   end
 
+  it 'focuses on the confirmation field' do
+    transfer_project(project, group, confirm: false)
+    expect(page).to have_selector '#confirm_name_input:focus'
+  end
+
   it 'allows transferring a project to a group' do
     old_path = project_path(project)
     transfer_project(project, group)
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 00946bccd9af7d66e0edacedf6f54429dae6d0e5..39b47d990403cefe1031c89fd0fd5b1dfa5cc69e 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -155,6 +155,12 @@ describe 'Project' do
       visit edit_project_path(project)
     end
 
+    it 'focuses on the confirmation field' do
+      click_button 'Remove project'
+
+      expect(page).to have_selector '#confirm_name_input:focus'
+    end
+
     it 'removes a project' do
       expect { remove_with_confirm('Remove project', project.path) }.to change { Project.count }.by(-1)
       expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted."
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 05f5d47ce42e6bc92ef78b8c24e27d2c6740c92e..0f3a95da5bf82b9d09ec04066e277b4a243e6905 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -1,7 +1,10 @@
 import Vue from 'vue';
+import Vuex from 'vuex';
+import diffsModule from '~/diffs/store/modules';
+import notesModule from '~/notes/stores/modules';
 import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
 import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
 
 const discussionFixture = 'merge_requests/diff_discussion.json';
 
@@ -9,6 +12,12 @@ describe('diff_file_header', () => {
   let vm;
   let props;
   const Component = Vue.extend(DiffFileHeader);
+  const store = new Vuex.Store({
+    modules: {
+      diffs: diffsModule,
+      notes: notesModule,
+    },
+  });
 
   beforeEach(() => {
     const diffDiscussionMock = getJSONFixture(discussionFixture)[0];
@@ -26,13 +35,13 @@ describe('diff_file_header', () => {
   describe('computed', () => {
     describe('icon', () => {
       beforeEach(() => {
-        props.diffFile.blob.icon = 'dummy icon';
+        props.diffFile.blob.icon = 'file-text-o';
       });
 
       it('returns the blob icon for files', () => {
         props.diffFile.submodule = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.icon).toBe(props.diffFile.blob.icon);
       });
@@ -40,7 +49,7 @@ describe('diff_file_header', () => {
       it('returns the archive icon for submodules', () => {
         props.diffFile.submodule = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.icon).toBe('archive');
       });
@@ -58,7 +67,7 @@ describe('diff_file_header', () => {
       it('returns the fileHash for files', () => {
         props.diffFile.submodule = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.titleLink).toBe(`#${props.diffFile.fileHash}`);
       });
@@ -66,7 +75,7 @@ describe('diff_file_header', () => {
       it('returns the submoduleTreeUrl for submodules', () => {
         props.diffFile.submodule = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.titleLink).toBe(props.diffFile.submoduleTreeUrl);
       });
@@ -77,7 +86,7 @@ describe('diff_file_header', () => {
           submoduleTreeUrl: null,
         });
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.titleLink).toBe(props.diffFile.submoduleLink);
       });
@@ -94,7 +103,7 @@ describe('diff_file_header', () => {
       it('returns the filePath for files', () => {
         props.diffFile.submodule = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.filePath).toBe(props.diffFile.filePath);
       });
@@ -102,7 +111,7 @@ describe('diff_file_header', () => {
       it('appends the truncated blob id for submodules', () => {
         props.diffFile.submodule = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.filePath).toBe(
           `${props.diffFile.filePath} @ ${props.diffFile.blob.id.substr(0, 8)}`,
@@ -114,7 +123,7 @@ describe('diff_file_header', () => {
       it('returns a link tag if fileHash is set', () => {
         props.diffFile.fileHash = 'some hash';
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.titleTag).toBe('a');
       });
@@ -122,7 +131,7 @@ describe('diff_file_header', () => {
       it('returns a span tag if fileHash is not set', () => {
         props.diffFile.fileHash = null;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.titleTag).toBe('span');
       });
@@ -137,7 +146,7 @@ describe('diff_file_header', () => {
       });
 
       it('returns true if file is stored in LFS', () => {
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.isUsingLfs).toBe(true);
       });
@@ -145,7 +154,7 @@ describe('diff_file_header', () => {
       it('returns false if file is not stored externally', () => {
         props.diffFile.storedExternally = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.isUsingLfs).toBe(false);
       });
@@ -153,7 +162,7 @@ describe('diff_file_header', () => {
       it('returns false if file is not stored in LFS', () => {
         props.diffFile.externalStorage = 'not lfs';
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.isUsingLfs).toBe(false);
       });
@@ -163,7 +172,7 @@ describe('diff_file_header', () => {
       it('returns chevron-down if the diff is expanded', () => {
         props.expanded = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.collapseIcon).toBe('chevron-down');
       });
@@ -171,49 +180,18 @@ describe('diff_file_header', () => {
       it('returns chevron-right if the diff is collapsed', () => {
         props.expanded = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.collapseIcon).toBe('chevron-right');
       });
     });
 
-    describe('isDiscussionsExpanded', () => {
-      beforeEach(() => {
-        Object.assign(props, {
-          discussionsExpanded: true,
-          expanded: true,
-        });
-      });
-
-      it('returns true if diff and discussion are expanded', () => {
-        vm = mountComponent(Component, props);
-
-        expect(vm.isDiscussionsExpanded).toBe(true);
-      });
-
-      it('returns false if discussion is collapsed', () => {
-        props.discussionsExpanded = false;
-
-        vm = mountComponent(Component, props);
-
-        expect(vm.isDiscussionsExpanded).toBe(false);
-      });
-
-      it('returns false if diff is collapsed', () => {
-        props.expanded = false;
-
-        vm = mountComponent(Component, props);
-
-        expect(vm.isDiscussionsExpanded).toBe(false);
-      });
-    });
-
     describe('viewFileButtonText', () => {
       it('contains the truncated content SHA', () => {
         const dummySha = 'deebd00f is no SHA';
         props.diffFile.contentSha = dummySha;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.viewFileButtonText).not.toContain(dummySha);
         expect(vm.viewFileButtonText).toContain(dummySha.substr(0, 8));
@@ -225,7 +203,7 @@ describe('diff_file_header', () => {
         const dummySha = 'deadabba sings no more';
         props.diffFile.diffRefs.baseSha = dummySha;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.viewReplacedFileButtonText).not.toContain(dummySha);
         expect(vm.viewReplacedFileButtonText).toContain(dummySha.substr(0, 8));
@@ -234,25 +212,25 @@ describe('diff_file_header', () => {
   });
 
   describe('methods', () => {
-    describe('handleToggle', () => {
+    describe('handleToggleFile', () => {
       beforeEach(() => {
         spyOn(vm, '$emit').and.stub();
       });
 
       it('emits toggleFile if checkTarget is false', () => {
-        vm.handleToggle(null, false);
+        vm.handleToggleFile(null, false);
 
         expect(vm.$emit).toHaveBeenCalledWith('toggleFile');
       });
 
       it('emits toggleFile if checkTarget is true and event target is header', () => {
-        vm.handleToggle({ target: vm.$refs.header }, true);
+        vm.handleToggleFile({ target: vm.$refs.header }, true);
 
         expect(vm.$emit).toHaveBeenCalledWith('toggleFile');
       });
 
       it('does not emit toggleFile if checkTarget is true and event target is not header', () => {
-        vm.handleToggle({ target: 'not header' }, true);
+        vm.handleToggleFile({ target: 'not header' }, true);
 
         expect(vm.$emit).not.toHaveBeenCalled();
       });
@@ -266,7 +244,7 @@ describe('diff_file_header', () => {
       it('is visible if collapsible is true', () => {
         props.collapsible = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(collapseToggle()).not.toBe(null);
       });
@@ -274,14 +252,14 @@ describe('diff_file_header', () => {
       it('is hidden if collapsible is false', () => {
         props.collapsible = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(collapseToggle()).toBe(null);
       });
     });
 
     it('displays an file icon in the title', () => {
-      vm = mountComponent(Component, props);
+      vm = mountComponentWithStore(Component, { props, store });
       expect(vm.$el.querySelector('svg.js-file-icon use').getAttribute('xlink:href')).toContain(
         'ruby',
       );
@@ -293,7 +271,7 @@ describe('diff_file_header', () => {
       it('displays the path of a added file', () => {
         props.diffFile.renamedFile = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(filePaths()).toHaveLength(1);
         expect(filePaths()[0]).toHaveText(props.diffFile.filePath);
@@ -303,7 +281,7 @@ describe('diff_file_header', () => {
         props.diffFile.renamedFile = false;
         props.diffFile.deletedFile = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(filePaths()).toHaveLength(1);
         expect(filePaths()[0]).toHaveText(`${props.diffFile.filePath} deleted`);
@@ -312,7 +290,7 @@ describe('diff_file_header', () => {
       it('displays old and new path if the file was renamed', () => {
         props.diffFile.renamedFile = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(filePaths()).toHaveLength(2);
         expect(filePaths()[0]).toHaveText(props.diffFile.oldPath);
@@ -321,7 +299,7 @@ describe('diff_file_header', () => {
     });
 
     it('displays a copy to clipboard button', () => {
-      vm = mountComponent(Component, props);
+      vm = mountComponentWithStore(Component, { props, store });
 
       const button = vm.$el.querySelector('.btn-clipboard');
       expect(button).not.toBe(null);
@@ -332,7 +310,7 @@ describe('diff_file_header', () => {
       it('it displays old and new file mode if it changed', () => {
         props.diffFile.modeChanged = true;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         const { fileMode } = vm.$refs;
         expect(fileMode).not.toBe(undefined);
@@ -343,7 +321,7 @@ describe('diff_file_header', () => {
       it('does not display the file mode if it has not changed', () => {
         props.diffFile.modeChanged = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         const { fileMode } = vm.$refs;
         expect(fileMode).toBe(undefined);
@@ -359,7 +337,7 @@ describe('diff_file_header', () => {
           externalStorage: 'lfs',
         });
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(lfsLabel()).not.toBe(null);
         expect(lfsLabel()).toHaveText('LFS');
@@ -368,7 +346,7 @@ describe('diff_file_header', () => {
       it('does not display the LFS label for files stored in repository', () => {
         props.diffFile.storedExternally = false;
 
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(lfsLabel()).toBe(null);
       });
@@ -376,7 +354,7 @@ describe('diff_file_header', () => {
 
     describe('edit button', () => {
       it('should not render edit button if addMergeRequestButtons is not true', () => {
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null);
       });
@@ -384,7 +362,7 @@ describe('diff_file_header', () => {
       it('should show edit button when file is editable', () => {
         props.addMergeRequestButtons = true;
         props.diffFile.editPath = '/';
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.$el.querySelector('.js-edit-blob')).toContainText('Edit');
       });
@@ -393,7 +371,7 @@ describe('diff_file_header', () => {
         props.addMergeRequestButtons = true;
         props.diffFile.deletedFile = true;
         props.diffFile.editPath = '/';
-        vm = mountComponent(Component, props);
+        vm = mountComponentWithStore(Component, { props, store });
 
         expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null);
       });
@@ -413,7 +391,7 @@ describe('diff_file_header', () => {
           props.diffFile.externalUrl = url;
           props.diffFile.formattedExternalUrl = title;
 
-          vm = mountComponent(Component, props);
+          vm = mountComponentWithStore(Component, { props, store });
 
           expect(vm.$el.querySelector(`a[href="${url}"]`)).not.toBe(null);
           expect(vm.$el.querySelector(`a[data-original-title="View on ${title}"]`)).not.toBe(null);
@@ -423,11 +401,39 @@ describe('diff_file_header', () => {
           props.diffFile.externalUrl = '';
           props.diffFile.formattedExternalUrl = title;
 
-          vm = mountComponent(Component, props);
+          vm = mountComponentWithStore(Component, { props, store });
 
           expect(vm.$el.querySelector(`a[data-original-title="View on ${title}"]`)).toBe(null);
         });
       });
     });
+
+    describe('handles toggle discussions', () => {
+      it('dispatches toggleFileDiscussions when user clicks on toggle discussions button', () => {
+        const propsCopy = Object.assign({}, props);
+        propsCopy.diffFile.submodule = false;
+        propsCopy.diffFile.blob = {
+          id: '848ed9407c6730ff16edb3dd24485a0eea24292a',
+          path: 'lib/base.js',
+          name: 'base.js',
+          mode: '100644',
+          readableText: true,
+          icon: 'file-text-o',
+        };
+        propsCopy.addMergeRequestButtons = true;
+        propsCopy.diffFile.deletedFile = true;
+
+        vm = mountComponentWithStore(Component, {
+          props: propsCopy,
+          store,
+        });
+
+        spyOn(vm, 'toggleFileDiscussions');
+
+        vm.$el.querySelector('.js-btn-vue-toggle-comments').click();
+
+        expect(vm.toggleFileDiscussions).toHaveBeenCalled();
+      });
+    });
   });
 });
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index 6829c1e956a9dd3d50e1a0c078360c78cb92005c..c1560dac1a0698ec6e0d5759b6db44dad26d4690 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -191,4 +191,48 @@ describe('DiffsStoreActions', () => {
       );
     });
   });
+
+  describe('toggleFileDiscussions', () => {
+    it('should dispatch collapseDiscussion when all discussions are expanded', () => {
+      const getters = {
+        getDiffFileDiscussions: jasmine.createSpy().and.returnValue([{ id: 1 }]),
+        diffHasAllExpandedDiscussions: jasmine.createSpy().and.returnValue(true),
+        diffHasAllCollpasedDiscussions: jasmine.createSpy().and.returnValue(false),
+      };
+
+      const dispatch = jasmine.createSpy('dispatch');
+
+      actions.toggleFileDiscussions({ getters, dispatch });
+
+      expect(dispatch).toHaveBeenCalledWith('collapseDiscussion', { discussionId: 1 }, { root: true });
+    });
+
+    it('should dispatch expandDiscussion when all discussions are collapsed', () => {
+      const getters = {
+        getDiffFileDiscussions: jasmine.createSpy().and.returnValue([{ id: 1 }]),
+        diffHasAllExpandedDiscussions: jasmine.createSpy().and.returnValue(false),
+        diffHasAllCollpasedDiscussions: jasmine.createSpy().and.returnValue(true),
+      };
+
+      const dispatch = jasmine.createSpy();
+
+      actions.toggleFileDiscussions({ getters, dispatch });
+
+      expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true });
+    });
+
+    it('should dispatch expandDiscussion when some discussions are collapsed and others are expanded for the collapsed discussion', () => {
+      const getters = {
+        getDiffFileDiscussions: jasmine.createSpy().and.returnValue([{ expanded: false, id: 1 }]),
+        diffHasAllExpandedDiscussions: jasmine.createSpy().and.returnValue(false),
+        diffHasAllCollpasedDiscussions: jasmine.createSpy().and.returnValue(false),
+      };
+
+      const dispatch = jasmine.createSpy();
+
+      actions.toggleFileDiscussions({ getters, dispatch });
+
+      expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true });
+    });
+  });
 });
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index 7a94f18778b2d16a38cad732ad57f25848e0d327..919b612bb6aeace17f7b86b92d833c59892cac14 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -1,12 +1,24 @@
 import * as getters from '~/diffs/store/getters';
 import state from '~/diffs/store/modules/diff_state';
 import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+import discussion from '../mock_data/diff_discussions';
 
-describe('DiffsStoreGetters', () => {
+describe('Diffs Module Getters', () => {
   let localState;
+  let discussionMock;
+  let discussionMock1;
+
+  const diffFileMock = {
+    fileHash: '9732849daca6ae818696d9575f5d1207d1a7f8bb',
+  };
 
   beforeEach(() => {
     localState = state();
+    discussionMock = Object.assign({}, discussion);
+    discussionMock.diff_file.file_hash = diffFileMock.fileHash;
+
+    discussionMock1 = Object.assign({}, discussion);
+    discussionMock1.diff_file.file_hash = diffFileMock.fileHash;
   });
 
   describe('isParallelView', () => {
@@ -63,4 +75,113 @@ describe('DiffsStoreGetters', () => {
       expect(getters.commitId(localState)).toEqual(null);
     });
   });
+
+  describe('diffHasAllExpandedDiscussions', () => {
+    it('returns true when all discussions are expanded', () => {
+      expect(
+        getters.diffHasAllExpandedDiscussions(localState, {
+          getDiffFileDiscussions: () => [discussionMock, discussionMock],
+        })(diffFileMock),
+      ).toEqual(true);
+    });
+
+    it('returns false when there are no discussions', () => {
+      expect(
+        getters.diffHasAllExpandedDiscussions(localState, {
+          getDiffFileDiscussions: () => [],
+        })(diffFileMock),
+      ).toEqual(false);
+    });
+
+    it('returns false when one discussions is collapsed', () => {
+      discussionMock1.expanded = false;
+
+      expect(
+        getters.diffHasAllExpandedDiscussions(localState, {
+          getDiffFileDiscussions: () => [discussionMock, discussionMock1],
+        })(diffFileMock),
+      ).toEqual(false);
+    });
+  });
+
+  describe('diffHasAllCollpasedDiscussions', () => {
+    it('returns true when all discussions are collapsed', () => {
+      discussionMock.diff_file.file_hash = diffFileMock.fileHash;
+      discussionMock.expanded = false;
+
+      expect(
+        getters.diffHasAllCollpasedDiscussions(localState, {
+          getDiffFileDiscussions: () => [discussionMock],
+        })(diffFileMock),
+      ).toEqual(true);
+    });
+
+    it('returns false when there are no discussions', () => {
+      expect(
+        getters.diffHasAllCollpasedDiscussions(localState, {
+          getDiffFileDiscussions: () => [],
+        })(diffFileMock),
+      ).toEqual(false);
+    });
+
+    it('returns false when one discussions is expanded', () => {
+      discussionMock1.expanded = false;
+
+      expect(
+        getters.diffHasAllCollpasedDiscussions(localState, {
+          getDiffFileDiscussions: () => [discussionMock, discussionMock1],
+        })(diffFileMock),
+      ).toEqual(false);
+    });
+  });
+
+  describe('diffHasExpandedDiscussions', () => {
+    it('returns true when one of the discussions is expanded', () => {
+      discussionMock1.expanded = false;
+
+      expect(
+        getters.diffHasExpandedDiscussions(localState, {
+          getDiffFileDiscussions: () => [discussionMock, discussionMock],
+        })(diffFileMock),
+      ).toEqual(true);
+    });
+
+    it('returns false when there are no discussions', () => {
+      expect(
+        getters.diffHasExpandedDiscussions(localState, { getDiffFileDiscussions: () => [] })(
+          diffFileMock,
+        ),
+      ).toEqual(false);
+    });
+
+    it('returns false when no discussion is expanded', () => {
+      discussionMock.expanded = false;
+      discussionMock1.expanded = false;
+
+      expect(
+        getters.diffHasExpandedDiscussions(localState, {
+          getDiffFileDiscussions: () => [discussionMock, discussionMock1],
+        })(diffFileMock),
+      ).toEqual(false);
+    });
+  });
+
+  describe('getDiffFileDiscussions', () => {
+    it('returns an array with discussions when fileHash matches and the discussion belongs to a diff', () => {
+      discussionMock.diff_file.file_hash = diffFileMock.fileHash;
+
+      expect(
+        getters.getDiffFileDiscussions(localState, {}, {}, { discussions: [discussionMock] })(
+          diffFileMock,
+        ).length,
+      ).toEqual(1);
+    });
+
+    it('returns an empty array when no discussions are found in the given diff', () => {
+      expect(
+        getters.getDiffFileDiscussions(localState, {}, {}, { discussions: [] })(diffFileMock)
+          .length,
+      ).toEqual(0);
+    });
+  });
 });
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 71ef3aa9b0396f3eabb8048ac98458f35f5e2825..b66e8e1ceb30871cdecdbd82e30612d8c998b46b 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -128,6 +128,19 @@ describe('Actions Notes Store', () => {
     });
   });
 
+  describe('collapseDiscussion', () => {
+    it('should commit collapse discussion', done => {
+      testAction(
+        actions.collapseDiscussion,
+        { discussionId: discussionMock.id },
+        { notes: [discussionMock] },
+        [{ type: 'COLLAPSE_DISCUSSION', payload: { discussionId: discussionMock.id } }],
+        [],
+        done,
+      );
+    });
+  });
+
   describe('async methods', () => {
     const interceptor = (request, next) => {
       next(
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index ccc7328447b428cb794ee10319c431c84d06f78b..a15ff1a58887b72690273d71157b8a22b961a299 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -74,6 +74,20 @@ describe('Notes Store mutations', () => {
     });
   });
 
+  describe('COLLAPSE_DISCUSSION', () => {
+    it('should collpase an expanded discussion', () => {
+      const discussion = Object.assign({}, discussionMock, { expanded: true });
+
+      const state = {
+        discussions: [discussion],
+      };
+
+      mutations.COLLAPSE_DISCUSSION(state, { discussionId: discussion.id });
+
+      expect(state.discussions[0].expanded).toEqual(false);
+    });
+  });
+
   describe('REMOVE_PLACEHOLDER_NOTES', () => {
     it('should remove all placeholder notes in indivudal notes and discussion', () => {
       const placeholderNote = Object.assign({}, individualNote, { isPlaceholderNote: true });
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index d94004db67cb96131c0cfeaaf42bcd2c449dcfe2..d114275c751602d4cbecf2e0cc480efe5ea35077 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -70,4 +70,19 @@ describe Gitlab::Kubernetes do
       it { is_expected.to eq(YAML.load_file(path)) }
     end
   end
+
+  describe '#add_terminal_auth' do
+    it 'adds authentication parameters to a hash' do
+      terminal = { original: 'value' }
+
+      add_terminal_auth(terminal, token: 'foo', max_session_time: 0, ca_pem: 'bar')
+
+      expect(terminal).to eq(
+        original: 'value',
+        headers: { 'Authorization' => ['Bearer foo'] },
+        max_session_time: 0,
+        ca_pem: 'bar'
+      )
+    end
+  end
 end