From c2872033329e90566e4c1b91c11c36e8746d59b1 Mon Sep 17 00:00:00 2001
From: Andrew Smith <gitlab@espadav8.co.uk>
Date: Thu, 22 Feb 2024 09:48:05 +0000
Subject: [PATCH] Move all Vue app init functions into a single location

We are going to need to combine these apps into a single `new Vue` call,
so having them in the same file to start with will make things much
easier later on.

Signed-off-by: Andrew Smith <espadav8@gmail.com>
---
 .../javascripts/forks/init_forks_button.js    |  41 ------
 .../components/more_actions_dropdown.vue      |  10 +-
 .../init_more_actions_dropdown.js             |   2 +-
 .../components/notifications_dropdown.vue     |   6 +-
 app/assets/javascripts/notifications/index.js |   2 +-
 .../home_panel/components/home_panel.vue      |  61 +++++++++
 .../pages/projects/home_panel/index.js        | 106 ++++++++++++++++
 .../javascripts/pages/projects/show/index.js  |  12 +-
 app/assets/javascripts/stars/index.js         |  40 ------
 app/controllers/projects_controller.rb        |   1 -
 app/helpers/projects_helper.rb                |  59 +++++++--
 app/views/groups/_home_panel.html.haml        |   2 +-
 .../_more_actions_dropdown.html.haml}         |   6 -
 app/views/projects/_home_panel.html.haml      |   7 +-
 app/views/projects/buttons/_fork.html.haml    |   3 -
 app/views/projects/buttons/_star.html.haml    |   1 -
 spec/controllers/projects_controller_spec.rb  |  29 -----
 .../components/more_actions_dropdown_spec.js  |   6 +-
 .../home_panel/components/home_panel_spec.js  |  65 ++++++++++
 spec/helpers/projects_helper_spec.rb          | 118 ++++++++++++++++++
 .../projects/_home_panel.html.haml_spec.rb    |  35 +-----
 21 files changed, 419 insertions(+), 193 deletions(-)
 delete mode 100644 app/assets/javascripts/forks/init_forks_button.js
 create mode 100644 app/assets/javascripts/pages/projects/home_panel/components/home_panel.vue
 create mode 100644 app/assets/javascripts/pages/projects/home_panel/index.js
 delete mode 100644 app/assets/javascripts/stars/index.js
 rename app/views/{shared/_groups_projects_more_actions_dropdown.html.haml => groups/_more_actions_dropdown.html.haml} (64%)
 delete mode 100644 app/views/projects/buttons/_fork.html.haml
 delete mode 100644 app/views/projects/buttons/_star.html.haml
 create mode 100644 spec/frontend/home_panel/components/home_panel_spec.js

diff --git a/app/assets/javascripts/forks/init_forks_button.js b/app/assets/javascripts/forks/init_forks_button.js
deleted file mode 100644
index b899d1c51dbf7..0000000000000
--- a/app/assets/javascripts/forks/init_forks_button.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import Vue from 'vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import ForksButton from './components/forks_button.vue';
-
-const initForksButton = () => {
-  const el = document.getElementById('js-forks-button');
-
-  if (!el) {
-    return false;
-  }
-
-  const {
-    forksCount,
-    projectFullPath,
-    projectForksUrl,
-    userForkUrl,
-    newForkUrl,
-    canReadCode,
-    canCreateFork,
-    canForkProject,
-  } = el.dataset;
-
-  return new Vue({
-    el,
-    provide: {
-      forksCount,
-      projectFullPath,
-      projectForksUrl,
-      userForkUrl,
-      newForkUrl,
-      canReadCode: parseBoolean(canReadCode),
-      canCreateFork: parseBoolean(canCreateFork),
-      canForkProject: parseBoolean(canForkProject),
-    },
-    render(createElement) {
-      return createElement(ForksButton);
-    },
-  });
-};
-
-export default initForksButton;
diff --git a/app/assets/javascripts/groups_projects/components/more_actions_dropdown.vue b/app/assets/javascripts/groups_projects/components/more_actions_dropdown.vue
index 238e382fabee8..8863481617712 100644
--- a/app/assets/javascripts/groups_projects/components/more_actions_dropdown.vue
+++ b/app/assets/javascripts/groups_projects/components/more_actions_dropdown.vue
@@ -23,7 +23,7 @@ export default {
   },
   inject: [
     'isGroup',
-    'id',
+    'groupOrProjectId',
     'leavePath',
     'leaveConfirmMessage',
     'withdrawPath',
@@ -90,7 +90,7 @@ export default {
     },
     copyIdItem() {
       return {
-        text: sprintf(this.copyTitle, { id: this.id }),
+        text: sprintf(this.copyTitle, { id: this.groupOrProjectId }),
         action: () => {
           this.$toast.show(this.copiedToClipboard);
         },
@@ -148,7 +148,11 @@ export default {
       </div>
     </template>
 
-    <gl-disclosure-dropdown-item v-if="id" :item="copyIdItem" :data-clipboard-text="id" />
+    <gl-disclosure-dropdown-item
+      v-if="groupOrProjectId"
+      :item="copyIdItem"
+      :data-clipboard-text="groupOrProjectId"
+    />
 
     <gl-disclosure-dropdown-group v-if="hasPath" bordered>
       <gl-disclosure-dropdown-item v-if="leavePath" ref="leaveItem" :item="leaveItem" />
diff --git a/app/assets/javascripts/groups_projects/init_more_actions_dropdown.js b/app/assets/javascripts/groups_projects/init_more_actions_dropdown.js
index 5d83f9ed3b2e7..c5f6d29d08079 100644
--- a/app/assets/javascripts/groups_projects/init_more_actions_dropdown.js
+++ b/app/assets/javascripts/groups_projects/init_more_actions_dropdown.js
@@ -24,7 +24,7 @@ export default function InitMoreActionsDropdown() {
     name: 'MoreActionsDropdownRoot',
     provide: {
       isGroup: parseBoolean(isGroup),
-      id,
+      groupOrProjectId: id,
       leavePath,
       leaveConfirmMessage,
       withdrawPath,
diff --git a/app/assets/javascripts/notifications/components/notifications_dropdown.vue b/app/assets/javascripts/notifications/components/notifications_dropdown.vue
index 6b450c2b5fd7c..eeca39fa5227a 100644
--- a/app/assets/javascripts/notifications/components/notifications_dropdown.vue
+++ b/app/assets/javascripts/notifications/components/notifications_dropdown.vue
@@ -21,7 +21,7 @@ export default {
     containerClass: {
       default: '',
     },
-    disabled: {
+    emailsDisabled: {
       default: false,
     },
     dropdownItems: {
@@ -81,7 +81,7 @@ export default {
         this.$options.i18n.notificationTitles[this.selectedNotificationLevel] ||
         this.selectedNotificationLevel;
 
-      return this.disabled
+      return this.emailsDisabled
         ? this.$options.i18n.notificationDescriptions.owner_disabled
         : sprintf(this.$options.i18n.notificationTooltipTitle, {
             notification_title: notificationTitle,
@@ -127,7 +127,7 @@ export default {
       :size="buttonSize"
       :icon="buttonIcon"
       :loading="isLoading"
-      :disabled="disabled"
+      :disabled="emailsDisabled"
       :split="isCustomNotification"
       :text="buttonText"
       :no-flip="noFlip"
diff --git a/app/assets/javascripts/notifications/index.js b/app/assets/javascripts/notifications/index.js
index d41b1d9585449..2bdd6717577e1 100644
--- a/app/assets/javascripts/notifications/index.js
+++ b/app/assets/javascripts/notifications/index.js
@@ -60,7 +60,7 @@ export default () => {
       provide: {
         containerClass,
         buttonSize,
-        disabled: parseBoolean(disabled),
+        emailsDisabled: parseBoolean(disabled),
         dropdownItems: JSON.parse(dropdownItems),
         initialNotificationLevel: notificationLevel,
         helpPagePath,
diff --git a/app/assets/javascripts/pages/projects/home_panel/components/home_panel.vue b/app/assets/javascripts/pages/projects/home_panel/components/home_panel.vue
new file mode 100644
index 0000000000000..72f7be4243527
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/home_panel/components/home_panel.vue
@@ -0,0 +1,61 @@
+<script>
+import { s__, sprintf } from '~/locale';
+import { isLoggedIn } from '~/lib/utils/common_utils';
+import ForksButton from '~/forks/components/forks_button.vue';
+import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
+import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
+import StarCount from '~/stars/components/star_count.vue';
+
+export default {
+  components: {
+    ForksButton,
+    MoreActionsDropdown,
+    NotificationsDropdown,
+    StarCount,
+  },
+  inject: {
+    canReadProject: {
+      default: false,
+    },
+    isProjectEmpty: {
+      default: false,
+    },
+    projectId: {
+      default: '',
+    },
+  },
+  data() {
+    return {
+      isLoggedIn: isLoggedIn(),
+    };
+  },
+  computed: {
+    canForkProject() {
+      return !this.isProjectEmpty && isLoggedIn() && this.canReadProject;
+    },
+    copyProjectId() {
+      return sprintf(s__('ProjectPage|Project ID: %{id}'), { id: this.projectId });
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="gl-display-flex gl-gap-3">
+    <template v-if="isLoggedIn && canReadProject">
+      <notifications-dropdown />
+    </template>
+
+    <star-count />
+
+    <forks-button v-if="canForkProject" />
+
+    <template v-if="canReadProject">
+      <span class="gl-sr-only" itemprop="identifier" data-testid="project-id-content">
+        {{ copyProjectId }}
+      </span>
+    </template>
+
+    <more-actions-dropdown />
+  </div>
+</template>
diff --git a/app/assets/javascripts/pages/projects/home_panel/index.js b/app/assets/javascripts/pages/projects/home_panel/index.js
new file mode 100644
index 0000000000000..071d3d81b7eca
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/home_panel/index.js
@@ -0,0 +1,106 @@
+import { GlToast } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+
+import { parseBoolean } from '~/lib/utils/common_utils';
+import HomePanel from './components/home_panel.vue';
+
+Vue.use(GlToast);
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+  defaultClient: createDefaultClient(),
+});
+
+const initHomePanel = () => {
+  const container = document.getElementById('js-home-panel');
+
+  if (container === null) {
+    return null;
+  }
+
+  const {
+    // HomePanel component
+    canReadProject,
+    isProjectEmpty,
+    projectId,
+
+    // Dropdown component
+    isGroup,
+    leaveConfirmMessage,
+    leavePath,
+    requestAccessPath,
+    withdrawConfirmMessage,
+    withdrawPath,
+
+    // Fork component
+    canCreateFork,
+    canForkProject,
+    canReadCode,
+    forksCount,
+    newForkUrl,
+    projectForksUrl,
+    projectFullPath,
+    userForkUrl,
+
+    // Notification component
+    emailsDisabled,
+    notificationDropdownItems,
+    notificationHelpPagePath,
+    notificationLevel,
+
+    // Star component
+    signInPath,
+    starCount,
+    starred,
+    starrersPath,
+  } = container.dataset;
+
+  return new Vue({
+    apolloProvider,
+    el: container,
+    name: 'HomePanelRoot',
+    provide: {
+      // HomePanel component
+      canReadProject: parseBoolean(canReadProject),
+      isProjectEmpty: parseBoolean(isProjectEmpty),
+      projectId,
+
+      // Dropdown component
+      groupOrProjectId: projectId,
+      isGroup: parseBoolean(isGroup),
+      leaveConfirmMessage,
+      leavePath,
+      requestAccessPath,
+      withdrawConfirmMessage,
+      withdrawPath,
+
+      // Fork component
+      canCreateFork: parseBoolean(canCreateFork),
+      canForkProject: parseBoolean(canForkProject),
+      canReadCode: parseBoolean(canReadCode),
+      forksCount: parseInt(forksCount, 10) || 0,
+      newForkUrl,
+      projectForksUrl,
+      projectFullPath,
+      userForkUrl,
+
+      // Notification component
+      dropdownItems: JSON.parse(notificationDropdownItems || null),
+      emailsDisabled: parseBoolean(emailsDisabled),
+      helpPagePath: notificationHelpPagePath,
+      initialNotificationLevel: notificationLevel,
+      noFlip: true,
+
+      // Star component
+      signInPath,
+      starCount: parseInt(starCount, 10) || 0,
+      starred: parseBoolean(starred),
+      starrersPath,
+    },
+    render: (createElement) => createElement(HomePanel),
+  });
+};
+
+export { initHomePanel };
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index f7833c038cbdf..b840df8708fcd 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -3,15 +3,12 @@ import { addShortcutsExtension } from '~/behaviors/shortcuts';
 import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
 import initClustersDeprecationAlert from '~/projects/clusters_deprecation_alert';
 import leaveByUrl from '~/namespaces/leave_by_url';
-import initVueNotificationsDropdown from '~/notifications';
-import initVueStarCount from '~/stars';
 import initTerraformNotification from '~/projects/terraform_notification';
 import { initUploadFileTrigger } from '~/projects/upload_file';
 import initReadMore from '~/read_more';
-import initForksButton from '~/forks/init_forks_button';
 import initAmbiguousRefModal from '~/ref/init_ambiguous_ref_modal';
-import InitMoreActionsDropdown from '~/groups_projects/init_more_actions_dropdown';
 import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
+import { initHomePanel } from '../home_panel';
 
 // Project show page loads different overview content based on user preferences
 if (document.getElementById('js-tree-list')) {
@@ -38,17 +35,14 @@ if (document.querySelector('.project-show-activity')) {
     .catch(() => {});
 }
 
-initVueNotificationsDropdown();
-initVueStarCount();
-
 addShortcutsExtension(ShortcutsNavigation);
 
 initUploadFileTrigger();
 initClustersDeprecationAlert();
 initTerraformNotification();
-
 initReadMore();
 initAmbiguousRefModal();
+initHomePanel();
 
 if (document.querySelector('.js-autodevops-banner')) {
   import(/* webpackChunkName: 'userCallOut' */ '~/user_callout')
@@ -62,8 +56,6 @@ if (document.querySelector('.js-autodevops-banner')) {
     .catch(() => {});
 }
 
-initForksButton();
-InitMoreActionsDropdown();
 leaveByUrl('project');
 
 const initCodeDropdown = () => {
diff --git a/app/assets/javascripts/stars/index.js b/app/assets/javascripts/stars/index.js
deleted file mode 100644
index e19b3a540210a..0000000000000
--- a/app/assets/javascripts/stars/index.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { GlToast } from '@gitlab/ui';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import createDefaultClient from '~/lib/graphql';
-import { parseBoolean } from '../lib/utils/common_utils';
-import StarCount from './components/star_count.vue';
-
-Vue.use(GlToast);
-Vue.use(VueApollo);
-
-const apolloProvider = new VueApollo({
-  defaultClient: createDefaultClient(),
-});
-
-export default () => {
-  const containers = document.querySelectorAll('.js-vue-star-count');
-
-  if (containers.length === 0) {
-    return false;
-  }
-
-  return containers.forEach((el) => {
-    const { projectId, starCount, starred, starrersPath, signInPath } = el.dataset;
-
-    return new Vue({
-      el,
-      provide: {
-        starred: parseBoolean(starred),
-        starCount,
-        projectId,
-        starrersPath,
-        signInPath,
-      },
-      render(h) {
-        return h(StarCount);
-      },
-      apolloProvider,
-    });
-  });
-};
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 1206b3f43a92a..c2adab7d6dc0c 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -196,7 +196,6 @@ def show
 
     respond_to do |format|
       format.html do
-        @notification_setting = current_user.notification_settings_for(@project) if current_user
         @project = @project.present(current_user: current_user)
         render_landing_page
       end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 52d582755ea9d..2e60fad4f775b 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -427,20 +427,21 @@ def able_to_see_forks_count?(project, user)
 
   def fork_button_data_attributes(project)
     return unless current_user
+    return if project.empty_repo?
 
     if current_user.already_forked?(project) && current_user.forkable_namespaces.size < 2
       user_fork_url = namespace_project_path(current_user, current_user.fork_of(project))
     end
 
     {
+      can_create_fork: can?(current_user, :create_fork).to_s,
+      can_fork_project: can?(current_user, :fork_project, project).to_s,
+      can_read_code: can?(current_user, :read_code, project).to_s,
       forks_count: project.forks_count,
-      project_full_path: project.full_path,
-      project_forks_url: project_forks_path(project),
-      user_fork_url: user_fork_url,
       new_fork_url: new_project_fork_path(project),
-      can_read_code: can?(current_user, :read_code, project).to_s,
-      can_fork_project: can?(current_user, :fork_project, project).to_s,
-      can_create_fork: can?(current_user, :create_fork).to_s
+      project_forks_url: project_forks_path(project),
+      project_full_path: project.full_path,
+      user_fork_url: user_fork_url
     }
   end
 
@@ -448,16 +449,48 @@ def star_count_data_attributes(project)
     starred = current_user ? current_user.starred?(project) : false
 
     {
-      data: {
-        project_id: project.id,
-        sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'),
-        star_count: project.star_count,
-        starred: starred.to_s,
-        starrers_path: project_starrers_path(project)
-      }
+      project_id: project.id,
+      sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'),
+      star_count: project.star_count,
+      starred: starred.to_s,
+      starrers_path: project_starrers_path(project)
     }
   end
 
+  def notification_data_attributes(project)
+    return unless current_user
+
+    notification_setting = current_user.notification_settings_for(project)
+    dropdown_items = notification_dropdown_items(notification_setting).to_json if notification_setting
+    notification_level = notification_setting.level if notification_setting
+
+    {
+      emails_disabled: project.emails_disabled?.to_s,
+      notification_dropdown_items: dropdown_items,
+      notification_help_page_path: help_page_path('user/profile/notifications'),
+      notification_level: notification_level
+    }
+  end
+
+  def home_panel_data_attributes
+    project = @project.is_a?(ProjectPresenter) ? @project.project : @project
+    dropdown_attributes = groups_projects_more_actions_dropdown_data(project) || {}
+    fork_button_attributes = fork_button_data_attributes(project) || {}
+    notification_attributes = notification_data_attributes(project) || {}
+    star_count_attributes = star_count_data_attributes(project)
+
+    {
+      can_read_project: can?(current_user, :read_project, project).to_s,
+      is_project_empty: project.empty_repo?.to_s,
+      project_id: project.id
+    }.merge(
+      dropdown_attributes,
+      fork_button_attributes,
+      notification_attributes,
+      star_count_attributes
+    )
+  end
+
   def import_from_bitbucket_message
     configure_oauth_import_message('Bitbucket', help_page_path("integration/bitbucket"))
   end
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 265e2c582f8e7..5984a21dd0cfd 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -28,7 +28,7 @@
             = render Pajamas::ButtonComponent.new(href: new_project_path(namespace_id: @group.id), variant: :confirm, button_options: { data: { testid: 'new-project-button' }, class: 'gl-sm-w-auto gl-w-full' }) do
               = _('New project')
 
-        = render 'shared/groups_projects_more_actions_dropdown', source: @group
+        = render 'groups/more_actions_dropdown', source: @group
 
   - if @group.description.present?
     .group-home-desc.mt-1
diff --git a/app/views/shared/_groups_projects_more_actions_dropdown.html.haml b/app/views/groups/_more_actions_dropdown.html.haml
similarity index 64%
rename from app/views/shared/_groups_projects_more_actions_dropdown.html.haml
rename to app/views/groups/_more_actions_dropdown.html.haml
index 5189dc54f0c07..21f523fe9446a 100644
--- a/app/views/shared/_groups_projects_more_actions_dropdown.html.haml
+++ b/app/views/groups/_more_actions_dropdown.html.haml
@@ -6,11 +6,5 @@
   %span.gl-sr-only{ itemprop: 'identifier', data: { testid: 'group-id-content' } }
     = s_('GroupPage|Group ID: %{id}') % { id: id }
 
-- elsif can?(current_user, :read_project, @project)
-  - id = @project.id
-
-  %span.gl-sr-only{ itemprop: 'identifier', data: { testid: 'project-id-content' } }
-    = s_('ProjectPage|Project ID: %{id}') % { id: id }
-
 - if id || current_user
   .js-groups-projects-more-actions-dropdown{ data: dropdown_data }
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index aea735e4690a3..2542720c3e922 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,5 +1,4 @@
 - empty_repo = @project.empty_repo?
-- emails_disabled = @project.emails_disabled?
 - ff_reorg_disabled = Feature.disabled?(:project_overview_reorg)
 
 %header.project-home-panel.js-show-on-project-root.gl-mt-5{ class: [("empty-project" if empty_repo)] }
@@ -19,12 +18,8 @@
       - if current_user
         - if current_user.admin?
           = link_button_to nil, [:admin, @project], icon: 'admin', title: _('View project in admin area'), data: {toggle: 'tooltip', placement: 'top', container: 'body'}
-        - if @notification_setting
-          .js-vue-notification-dropdown{ data: { disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), project_id: @project.id, no_flip: 'true' } }
 
-      = render 'projects/buttons/star', project: @project
-      = render 'projects/buttons/fork'
-      = render 'shared/groups_projects_more_actions_dropdown', source: @project
+      #js-home-panel{ data: home_panel_data_attributes }
 
   - if ff_reorg_disabled
     - if can?(current_user, :read_code, @project)
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
deleted file mode 100644
index 963c416ed426a..0000000000000
--- a/app/views/projects/buttons/_fork.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- unless @project.empty_repo?
-  - if current_user
-    #js-forks-button{ data: fork_button_data_attributes(@project) }
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
deleted file mode 100644
index c081ab6105c58..0000000000000
--- a/app/views/projects/buttons/_star.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.js-vue-star-count{ star_count_data_attributes(@project) }
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 94058fbc21a32..f7cb9c8814229 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -173,40 +173,11 @@ def get_activity(project)
         sign_in(user)
       end
 
-      context "user does not have access to project" do
-        let(:private_project) { create(:project, :private) }
-
-        it "does not initialize notification setting" do
-          get :show, params: { namespace_id: private_project.namespace, id: private_project }
-          expect(assigns(:notification_setting)).to be_nil
-        end
-      end
-
       context "user has access to project" do
         before do
           expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
         end
 
-        context "and does not have notification setting" do
-          it "initializes notification as disabled" do
-            get :show, params: { namespace_id: public_project.namespace, id: public_project }
-            expect(assigns(:notification_setting).level).to eq("global")
-          end
-        end
-
-        context "and has notification setting" do
-          before do
-            setting = user.notification_settings_for(public_project)
-            setting.level = :watch
-            setting.save!
-          end
-
-          it "shows current notification setting" do
-            get :show, params: { namespace_id: public_project.namespace, id: public_project }
-            expect(assigns(:notification_setting).level).to eq("watch")
-          end
-        end
-
         context 'when ambiguous_ref_modal is disabled' do
           before do
             stub_feature_flags(ambiguous_ref_modal: false)
diff --git a/spec/frontend/groups_projects/components/more_actions_dropdown_spec.js b/spec/frontend/groups_projects/components/more_actions_dropdown_spec.js
index 777190149d105..3595038c03dc5 100644
--- a/spec/frontend/groups_projects/components/more_actions_dropdown_spec.js
+++ b/spec/frontend/groups_projects/components/more_actions_dropdown_spec.js
@@ -13,7 +13,7 @@ describe('moreActionsDropdown', () => {
     wrapper = shallowMountExtended(moreActionsDropdown, {
       provide: {
         isGroup: false,
-        id: 1,
+        groupOrProjectId: 1,
         leavePath: '',
         leaveConfirmMessage: '',
         withdrawPath: '',
@@ -39,7 +39,7 @@ describe('moreActionsDropdown', () => {
       beforeEach(async () => {
         createComponent({
           provideData: {
-            id: 22,
+            groupOrProjectId: 22,
           },
         });
         await showDropdown();
@@ -60,7 +60,7 @@ describe('moreActionsDropdown', () => {
         createComponent({
           provideData: {
             isGroup: true,
-            id: 11,
+            groupOrProjectId: 11,
           },
         });
         await showDropdown();
diff --git a/spec/frontend/home_panel/components/home_panel_spec.js b/spec/frontend/home_panel/components/home_panel_spec.js
new file mode 100644
index 0000000000000..ce2b14088cf3d
--- /dev/null
+++ b/spec/frontend/home_panel/components/home_panel_spec.js
@@ -0,0 +1,65 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import HomePanel from '~/pages/projects/home_panel/components/home_panel.vue';
+import ForksButton from '~/forks/components/forks_button.vue';
+import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
+import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
+import StarCount from '~/stars/components/star_count.vue';
+
+describe('HomePanel', () => {
+  let wrapper;
+
+  const createComponent = ({ isLoggedIn = false, provide = {} } = {}) => {
+    if (isLoggedIn) {
+      window.gon.current_user_id = 1;
+    }
+
+    wrapper = shallowMountExtended(HomePanel, {
+      provide: {
+        ...provide,
+      },
+    });
+  };
+
+  const findForksButton = () => wrapper.findComponent(ForksButton);
+  const findMoreActionsDropdown = () => wrapper.findComponent(MoreActionsDropdown);
+  const findNotificationsDropdown = () => wrapper.findComponent(NotificationsDropdown);
+  const findStarCount = () => wrapper.findComponent(StarCount);
+
+  describe.each`
+    isLoggedIn | canReadProject | isProjectEmpty | isForkButtonVisible | isMoreActionsDropdownVisible | isNotificationDropdownVisible | isStarCountVisible
+    ${true}    | ${true}        | ${true}        | ${false}            | ${true}                      | ${true}                       | ${true}
+    ${true}    | ${true}        | ${false}       | ${true}             | ${true}                      | ${true}                       | ${true}
+    ${true}    | ${false}       | ${true}        | ${false}            | ${true}                      | ${false}                      | ${true}
+    ${true}    | ${false}       | ${false}       | ${false}            | ${true}                      | ${false}                      | ${true}
+    ${false}   | ${true}        | ${true}        | ${false}            | ${true}                      | ${false}                      | ${true}
+    ${false}   | ${true}        | ${false}       | ${false}            | ${true}                      | ${false}                      | ${true}
+    ${false}   | ${false}       | ${true}        | ${false}            | ${true}                      | ${false}                      | ${true}
+    ${false}   | ${false}       | ${false}       | ${false}            | ${true}                      | ${false}                      | ${true}
+  `(
+    'renders components',
+    ({
+      isLoggedIn,
+      canReadProject,
+      isProjectEmpty,
+      isForkButtonVisible,
+      isMoreActionsDropdownVisible,
+      isNotificationDropdownVisible,
+      isStarCountVisible,
+    }) => {
+      it('as expected', () => {
+        createComponent({
+          isLoggedIn,
+          provide: {
+            canReadProject,
+            isProjectEmpty,
+          },
+        });
+
+        expect(findForksButton().exists()).toBe(isForkButtonVisible);
+        expect(findMoreActionsDropdown().exists()).toBe(isMoreActionsDropdownVisible);
+        expect(findNotificationsDropdown().exists()).toBe(isNotificationDropdownVisible);
+        expect(findStarCount().exists()).toBe(isStarCountVisible);
+      });
+    },
+  );
+});
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 31793dddd1fa9..27e7aab988ec7 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1078,6 +1078,124 @@ def license_name
     end
   end
 
+  describe '#star_count_data_attributes' do
+    before do
+      allow(user).to receive(:starred?).with(project).and_return(starred)
+      allow(helper).to receive(:new_session_path).and_return(sign_in_path)
+      allow(project).to receive(:star_count).and_return(5)
+    end
+
+    let(:sign_in_path) { 'sign/in/path' }
+    let(:common_data_attributes) do
+      {
+        project_id: project.id,
+        sign_in_path: sign_in_path,
+        star_count: 5,
+        starrers_path: "/#{project.full_path}/-/starrers"
+      }
+    end
+
+    subject { helper.star_count_data_attributes(project) }
+
+    context 'when user has already starred the project' do
+      let(:starred) { true }
+      let(:expected) { common_data_attributes.merge({ starred: "true" }) }
+
+      it { is_expected.to eq(expected) }
+    end
+
+    context 'when user has not starred the project' do
+      let(:starred) { false }
+      let(:expected) { common_data_attributes.merge({ starred: "false" }) }
+
+      it { is_expected.to eq(expected) }
+    end
+  end
+
+  describe '#notification_data_attributes' do
+    before do
+      allow(helper).to receive(:help_page_path).and_return(notification_help_path)
+      allow(project).to receive(:emails_disabled?).and_return(false)
+    end
+
+    let(:notification_help_path) { 'notification/help/path' }
+    let(:notification_dropdown_items) { '["global","watch","participating","mention","disabled"]' }
+
+    context "returns default user notification settings" do
+      let(:expected) do
+        {
+          emails_disabled: "false",
+          notification_dropdown_items: notification_dropdown_items,
+          notification_help_page_path: notification_help_path,
+          notification_level: "global"
+        }
+      end
+
+      subject { helper.notification_data_attributes(project) }
+
+      it { is_expected.to eq(expected) }
+    end
+
+    context "returns configured users notification settings" do
+      before do
+        allow(project).to receive(:emails_disabled?).and_return(true)
+        setting = user.notification_settings_for(project)
+        setting.level = :watch
+        setting.save!
+      end
+
+      let(:expected) do
+        {
+          emails_disabled: "true",
+          notification_dropdown_items: notification_dropdown_items,
+          notification_help_page_path: notification_help_path,
+          notification_level: "watch"
+        }
+      end
+
+      subject { helper.notification_data_attributes(project) }
+
+      it { is_expected.to eq(expected) }
+    end
+  end
+
+  describe '#home_panel_data_attributes' do
+    using RSpec::Parameterized::TableSyntax
+
+    before do
+      allow(helper).to receive(:groups_projects_more_actions_dropdown_data).and_return(nil)
+      allow(helper).to receive(:fork_button_data_attributes).and_return(nil)
+      allow(helper).to receive(:notification_data_attributes).and_return(nil)
+      allow(helper).to receive(:star_count_data_attributes).and_return({})
+    end
+
+    where(:can_read_project, :is_empty_repo) do
+      true  | true
+      false | false
+    end
+
+    with_them do
+      context "returns default user project details" do
+        before do
+          allow(helper).to receive(:can?).with(user, :read_project, project).and_return(can_read_project)
+          allow(project).to receive(:empty_repo?).and_return(is_empty_repo)
+        end
+
+        let(:expected) do
+          {
+            can_read_project: can_read_project.to_s,
+            is_project_empty: is_empty_repo.to_s,
+            project_id: project.id
+          }
+        end
+
+        subject { helper.home_panel_data_attributes }
+
+        it { is_expected.to eq(expected) }
+      end
+    end
+  end
+
   shared_examples 'configure import method modal' do
     context 'as a user' do
       it 'returns a link to contact an administrator' do
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index 0282e149b2552..2f80eab613e3e 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -37,44 +37,17 @@
     end
   end
 
-  context 'notifications' do
+  context 'home panel' do
     let(:project) { create(:project) }
 
     before do
       assign(:project, project)
-
-      allow(view).to receive(:current_user).and_return(user)
-      allow(view).to receive(:can?).with(user, :read_project, project).and_return(false)
-      allow(project).to receive(:license_anchor_data).and_return(false)
-    end
-
-    context 'when user is signed in' do
-      let(:user) { create(:user) }
-
-      before do
-        notification_settings = user.notification_settings_for(project)
-        assign(:notification_setting, notification_settings)
-      end
-
-      it 'renders Vue app root' do
-        render
-
-        expect(rendered).to have_selector('.js-vue-notification-dropdown')
-      end
     end
 
-    context 'when user is signed out' do
-      let(:user) { nil }
-
-      before do
-        assign(:notification_setting, nil)
-      end
-
-      it 'does not render Vue app root' do
-        render
+    it 'renders Vue app root' do
+      render
 
-        expect(rendered).not_to have_selector('.js-vue-notification-dropdown')
-      end
+      expect(rendered).to have_selector('#js-home-panel')
     end
   end
 
-- 
GitLab