From 066ca76baf17e3adaca89966a49923547867b9a1 Mon Sep 17 00:00:00 2001
From: Wu Jeremy <jeremyw@jihulab.com>
Date: Thu, 20 Jul 2023 16:54:29 +0000
Subject: [PATCH] Feat: implement rss functionality for profile edit overflow
 menu

* Add RSS subscription link to the newly introduced overflow menu.
* Add test cases against the addition.
---
 .../actions/components/user_actions_app.vue   | 26 ++++++++-
 .../users/profile/actions/index.js            |  3 +-
 app/views/users/show.html.haml                |  2 +-
 spec/features/users/rss_spec.rb               | 57 +++++++++++++------
 .../components/user_actions_app_spec.js       | 31 +++++++++-
 5 files changed, 96 insertions(+), 23 deletions(-)

diff --git a/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue b/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue
index bf983d911ea6..a8ecc014a959 100644
--- a/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue
+++ b/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue
@@ -11,13 +11,17 @@ export default {
       type: String,
       required: true,
     },
+    rssSubscriptionPath: {
+      type: String,
+      required: true,
+    },
   },
   data() {
     return {
-      // Only implement the copy function in MR for now
+      // Only implement the copy function and RSS subscription in MR for now
       // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122971
       // The rest will be implemented in the upcoming MR.
-      dropdownItems: [
+      defaultDropdownItems: [
         {
           action: this.onUserIdCopy,
           text: sprintf(this.$options.i18n.userId, { id: this.userId }),
@@ -28,6 +32,23 @@ export default {
       ],
     };
   },
+  computed: {
+    dropdownItems() {
+      if (this.rssSubscriptionPath) {
+        return [
+          ...this.defaultDropdownItems,
+          {
+            href: this.rssSubscriptionPath,
+            text: this.$options.i18n.rssSubscribe,
+            extraAttrs: {
+              'data-testid': 'user-profile-rss-subscription-link',
+            },
+          },
+        ];
+      }
+      return this.defaultDropdownItems;
+    },
+  },
   methods: {
     onUserIdCopy() {
       this.$toast.show(this.$options.i18n.userIdCopied);
@@ -36,6 +57,7 @@ export default {
   i18n: {
     userId: s__('UserProfile|Copy user ID: %{id}'),
     userIdCopied: s__('UserProfile|User ID copied to clipboard'),
+    rssSubscribe: s__('UserProfile|Subscribe'),
   },
 };
 </script>
diff --git a/app/assets/javascripts/users/profile/actions/index.js b/app/assets/javascripts/users/profile/actions/index.js
index 37a3faf82a50..22507165eced 100644
--- a/app/assets/javascripts/users/profile/actions/index.js
+++ b/app/assets/javascripts/users/profile/actions/index.js
@@ -7,7 +7,7 @@ export const initUserActionsApp = () => {
 
   if (!mountingEl) return false;
 
-  const { userId } = mountingEl.dataset;
+  const { userId, rssSubscriptionPath } = mountingEl.dataset;
 
   Vue.use(GlToast);
 
@@ -18,6 +18,7 @@ export const initUserActionsApp = () => {
       return createElement(UserActionsApp, {
         props: {
           userId,
+          rssSubscriptionPath,
         },
       });
     },
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 380d6aacb848..810a2ca36070 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -26,7 +26,7 @@
               = s_("UserProfile|Edit profile")
           = render 'users/view_gpg_keys'
           = render 'users/view_user_in_admin_area'
-          .js-user-profile-actions{ data: { user_id: @user.id } }
+          .js-user-profile-actions{ data: { user_id: @user.id, rss_subscription_path: can?(current_user, :read_user_profile, @user) ? user_path(@user, rss_url_options) : '' } }
       - else
         = render layout: 'users/cover_controls' do
           - if @user == current_user
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 39b6d049e435..2db58ce04a1e 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -6,28 +6,53 @@
   let(:user) { create(:user) }
   let(:path) { user_path(create(:user)) }
 
-  before do
-    stub_feature_flags(user_profile_overflow_menu_vue: false)
-  end
-
-  context 'when signed in' do
+  describe 'with "user_profile_overflow_menu_vue" feature flag off' do
     before do
-      sign_in(user)
-      visit path
+      stub_feature_flags(user_profile_overflow_menu_vue: false)
     end
 
-    it_behaves_like "it has an RSS button with current_user's feed token"
-  end
+    context 'when signed in' do
+      before do
+        sign_in(user)
+        visit path
+      end
 
-  context 'when signed out' do
-    before do
-      visit path
+      it_behaves_like "it has an RSS button with current_user's feed token"
     end
 
-    it_behaves_like "it has an RSS button without a feed token"
+    context 'when signed out' do
+      before do
+        visit path
+      end
+
+      it_behaves_like "it has an RSS button without a feed token"
+    end
   end
 
-  # TODO: implement tests before the FF "user_profile_overflow_menu_vue" is turned on
-  # See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122971
-  # Related Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/416974
+  describe 'with "user_profile_overflow_menu_vue" feature flag on', :js do
+    context 'when signed in' do
+      before do
+        sign_in(user)
+        visit path
+      end
+
+      it 'shows the RSS link with overflow menu' do
+        find('[data-testid="base-dropdown-toggle"').click
+
+        expect(page).to have_link 'Subscribe', href: /feed_token=glft-.*-#{user.id}/
+      end
+    end
+
+    context 'when signed out' do
+      before do
+        visit path
+      end
+
+      it 'has an RSS without a feed token' do
+        find('[data-testid="base-dropdown-toggle"').click
+
+        expect(page).not_to have_link 'Subscribe', href: /feed_token=glft-.*-#{user.id}/
+      end
+    end
+  end
 end
diff --git a/spec/frontend/users/profile/actions/components/user_actions_app_spec.js b/spec/frontend/users/profile/actions/components/user_actions_app_spec.js
index d27962440ee1..c2ae3d8364f2 100644
--- a/spec/frontend/users/profile/actions/components/user_actions_app_spec.js
+++ b/spec/frontend/users/profile/actions/components/user_actions_app_spec.js
@@ -6,11 +6,13 @@ describe('User Actions App', () => {
   let wrapper;
 
   const USER_ID = 'test-id';
+  const DEFAULT_SUBSCRIPTION_PATH = '';
 
   const createWrapper = (propsData = {}) => {
     wrapper = mountExtended(UserActionsApp, {
       propsData: {
         userId: USER_ID,
+        rssSubscriptionPath: DEFAULT_SUBSCRIPTION_PATH,
         ...propsData,
       },
     });
@@ -19,15 +21,25 @@ describe('User Actions App', () => {
   const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
   const findActions = () => wrapper.findAllByTestId('disclosure-dropdown-item');
   const findAction = (position = 0) => findActions().at(position);
+  const findSubscriptionLink = () => wrapper.findByTestId('user-profile-rss-subscription-link');
 
   it('shows dropdown', () => {
     createWrapper();
     expect(findDropdown().exists()).toBe(true);
   });
 
-  it('shows actions correctly', () => {
-    createWrapper();
-    expect(findActions()).toHaveLength(1);
+  describe('shows user action items', () => {
+    it('should show items without RSS subscriptions', () => {
+      createWrapper();
+      expect(findActions()).toHaveLength(1);
+    });
+
+    it('should show items with RSS subscriptions', () => {
+      createWrapper({
+        rssSubscriptionPath: '/test/path',
+      });
+      expect(findActions()).toHaveLength(2);
+    });
   });
 
   it('shows copy user id action', () => {
@@ -35,4 +47,17 @@ describe('User Actions App', () => {
     expect(findAction().text()).toBe(`Copy user ID: ${USER_ID}`);
     expect(findAction().findComponent('button').attributes('data-clipboard-text')).toBe(USER_ID);
   });
+
+  it('shows subscription link when subscription url was presented', () => {
+    const testSubscriptionPath = '/test/path';
+
+    createWrapper({
+      rssSubscriptionPath: testSubscriptionPath,
+    });
+
+    const rssLink = findSubscriptionLink();
+    expect(rssLink.exists()).toBe(true);
+    expect(rssLink.attributes('href')).toBe(testSubscriptionPath);
+    expect(rssLink.text()).toBe('Subscribe');
+  });
 });
-- 
GitLab