' }).html_safe
+ - if @user.bio.present?
+ %p.profile-user-bio.gl-mb-0
+ = @user.bio
+
+ - if @user.achievements_enabled && Ability.allowed?(current_user, :read_user_profile, @user)
+ #js-user-achievements{ data: { root_url: root_url, user_id: @user.id } }
+
+ - user_local_time = local_time(@user.timezone)
+ %div{ itemprop: 'address', itemscope: true, itemtype: 'https://schema.org/PostalAddress' }
+ %h2.gl-font-base.gl-mb-2.gl-mt-4= s_('UserProfile|Info')
+ - if work_information(@user).present?
+ .gl-mb-2
+ = sprite_icon('work', css_class: 'fgray')
+ %span.gl-ml-1
+ = work_information(@user, with_schema_markup: true)
- if @user.location.present?
- = render 'middle_dot_divider', stacking: true, itemprop: 'address', itemscope: true, itemtype: 'https://schema.org/PostalAddress' do
+ .gl-mb-2
= sprite_icon('location', css_class: 'fgray')
- %span{ itemprop: 'addressLocality' }
+ %span.gl-ml-1{ itemprop: 'addressLocality' }
= @user.location
- if user_local_time.present?
- = render 'middle_dot_divider', stacking: true, data: { testid: 'user-local-time' } do
+ .gl-mb-2{ data: { testid: 'user-local-time' } }
= sprite_icon('clock', css_class: 'fgray')
- %span
+ %span.gl-ml-1
= user_local_time
- - if work_information(@user).present?
- = render 'middle_dot_divider', stacking: true do
- = sprite_icon('work', css_class: 'fgray')
- %span
- = work_information(@user, with_schema_markup: true)
- .gl-text-gray-900
- - if @user.skype.present?
- = render 'middle_dot_divider' do
- = link_to "skype:#{@user.skype}", class: 'gl-hover-text-decoration-none', title: "Skype" do
- = sprite_icon('skype', css_class: 'skype-icon')
- - if @user.linkedin.present?
- = render 'middle_dot_divider' do
- = link_to linkedin_url(@user), class: 'gl-hover-text-decoration-none', title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow' do
- = sprite_icon('linkedin', css_class: 'linkedin-icon')
- - if @user.twitter.present?
- = render 'middle_dot_divider', breakpoint: 'sm' do
- = link_to twitter_url(@user), class: 'gl-hover-text-decoration-none', title: _("X (formerly Twitter)"), target: '_blank', rel: 'noopener noreferrer nofollow' do
- = sprite_icon('x', css_class: 'x-icon')
- - if @user.discord.present?
- = render 'middle_dot_divider', breakpoint: 'sm' do
- = link_to discord_url(@user), class: 'gl-hover-text-decoration-none', title: "Discord", target: '_blank', rel: 'noopener noreferrer nofollow' do
- = sprite_icon('discord', css_class: 'discord-icon')
- - if @user.mastodon.present?
- = render 'middle_dot_divider', breakpoint: 'sm' do
- = link_to mastodon_url(@user), class: 'gl-hover-text-decoration-none', title: "Mastodon", target: '_blank', rel: 'noopener noreferrer nofollow' do
- = sprite_icon('mastodon', css_class: 'mastodon-icon')
- - if @user.website_url.present?
- = render 'middle_dot_divider', stacking: true do
- - if Feature.enabled?(:security_auto_fix) && @user.bot?
- = sprite_icon('question-o', css_class: 'gl-text-blue-500')
- = link_to @user.short_website_url, @user.full_website_url, target: '_blank', rel: 'me noopener noreferrer nofollow', itemprop: 'url'
- - if display_public_email?(@user)
- = render 'middle_dot_divider', stacking: true do
- = link_to @user.public_email, "mailto:#{@user.public_email}", itemprop: 'email'
-
- -# Ensure this stays indented one level less than the social links
- -# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118314
- - if @user.bio.present? && @user.confirmed? && !@user.blocked?
- %p.profile-user-bio.gl-mb-3
- = @user.bio
+ = sprite_icon('calendar', css_class: 'fgray')
+ %span.gl-ml-1= s_('Member since %{date}') % { date: l(@user.created_at.to_date, format: :long) }
+
+ - if @user.website_url.present? || display_public_email?(@user) || @user.skype.present? || @user.linkedin.present? || @user.twitter.present? || @user.mastodon.present? || @user.discord.present?
+ .gl-text-gray-900
+ %h2.gl-font-base.gl-mb-2.gl-mt-4= s_('UserProfile|Contact')
+ - if @user.website_url.present?
+ .gl-mb-2
+ - if Feature.enabled?(:security_auto_fix) && @user.bot?
+ = sprite_icon('question-o', css_class: 'gl-text-blue-500')
+ = sprite_icon('earth', css_class: 'fgray')
+ = link_to @user.short_website_url, @user.full_website_url, class: 'gl-text-gray-900 gl-ml-1', target: '_blank', rel: 'me noopener noreferrer nofollow', itemprop: 'url'
+ - if display_public_email?(@user)
+ .gl-mb-2
+ = sprite_icon('mail', css_class: 'fgray')
+ = link_to @user.public_email, "mailto:#{@user.public_email}", class: 'gl-text-gray-900 gl-ml-1', itemprop: 'email'
+ - if @user.skype.present?
+ .gl-mb-2
+ = sprite_icon('skype', css_class: 'fgray')
+ = link_to @user.skype, "skype:#{@user.skype}", class: 'gl-text-gray-900 gl-ml-1', title: "Skype"
+ - if @user.linkedin.present?
+ .gl-mb-2
+ = sprite_icon('linkedin', css_class: 'fgray')
+ = link_to @user.linkedin, linkedin_url(@user), class: 'gl-text-gray-900 gl-ml-1', title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow'
+ - if @user.twitter.present?
+ .gl-mb-2
+ = sprite_icon('x', css_class: 'fgray')
+ = link_to @user.twitter, twitter_url(@user), class: 'gl-text-gray-900 gl-ml-1', title: _("X (formerly Twitter)"), target: '_blank', rel: 'noopener noreferrer nofollow'
+ - if @user.mastodon.present?
+ .gl-mb-2
+ = sprite_icon('mastodon', css_class: 'fgray')
+ = link_to @user.mastodon, mastodon_url(@user), class: 'gl-text-gray-900 gl-ml-1', title: "Mastodon", target: '_blank', rel: 'noopener noreferrer nofollow'
+ - if @user.discord.present?
+ .gl-mb-2
+ = sprite_icon('discord', css_class: 'fgray')
+ = link_to @user.discord, discord_url(@user), class: 'gl-text-gray-900 gl-ml-1', title: "Discord", target: '_blank', rel: 'noopener noreferrer nofollow'
-# TODO: Remove this with the removal of the old navigation.
-# See https://gitlab.com/gitlab-org/gitlab/-/issues/435899.
@@ -144,7 +209,7 @@
= s_('UserProfile|Personal projects')
- if profile_tab?(:starred)
%li.js-starred-tab
- = link_to user_starred_projects_path, data: { target: 'div#starred', action: 'starred', toggle: 'tab', endpoint: user_starred_projects_path(format: :json) } do
+ = link_to user_starred_projects_path, data: { target: 'div#starred', action: 'starred', toggle: 'tab', endpoint: user_starred_projects_path(format: :json), card_mode: true } do
= s_('UserProfile|Starred projects')
- if profile_tab?(:snippets)
%li.js-snippets-tab
@@ -157,67 +222,7 @@
= gl_badge_tag @user.followers.count, size: :sm
- if profile_tab?(:following)
%li.js-following-tab
- = link_to user_following_path, data: { target: 'div#following', action: 'following', toggle: 'tab', endpoint: user_following_path(format: :json) } do
+ = link_to user_following_path, data: { target: 'div#following', action: 'following', toggle: 'tab', endpoint: user_following_path(format: :json), testid: 'following_tab' } do
= s_('UserProfile|Following')
= gl_badge_tag @user.followees.count, size: :sm
- - if !profile_tabs.empty? && Feature.enabled?(:profile_tabs_vue, current_user)
- #js-profile-tabs{ data: user_profile_tabs_app_data(@user) }
- %div{ class: container_class }
- - unless Feature.enabled?(:profile_tabs_vue, current_user)
- .tab-content
- - if profile_tab?(:overview)
- #js-overview.tab-pane
- = render "users/overview"
-
- - if profile_tab?(:activity)
- #activity.tab-pane
- .row
- .col-12
- .flash-container
- - if can?(current_user, :read_cross_project)
- %h4.prepend-top-20
- = s_('UserProfile|Most Recent Activity')
- .content_list.user-activity-content{ data: { href: user_activity_path } }
- .loading
- = gl_loading_icon(size: 'md')
- - unless @user.bot?
- - if profile_tab?(:groups)
- #groups.tab-pane
- -# This tab is always loaded via AJAX
-
- - if profile_tab?(:contributed)
- #contributed.tab-pane
- -# This tab is always loaded via AJAX
-
- - if profile_tab?(:projects)
- #projects.tab-pane
- -# This tab is always loaded via AJAX
-
- - if profile_tab?(:starred)
- #starred.tab-pane
- -# This tab is always loaded via AJAX
-
- - if profile_tab?(:snippets)
- #snippets.tab-pane
- -# This tab is always loaded via AJAX
-
- - if profile_tab?(:followers)
- #followers.tab-pane
- -# This tab is always loaded via AJAX
-
- - if profile_tab?(:following)
- #following.tab-pane
- -# This tab is always loaded via AJAX
-
- .loading.hide
- .gl-spinner.gl-spinner-md
-
- - if profile_tabs.empty?
- .svg-content
- = image_tag 'illustrations/profile_private_mode.svg'
- .text-content.text-center
- %h4
- - if @user.blocked?
- = s_('UserProfile|This user is blocked')
- - else
- = s_('UserProfile|This user has a private profile')
+
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 108921089fe4ef32cc5354c452ea45142012efd6..c97bfe8e8d0384d9bd98f970a1cb1f7a31f0d98d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2722,6 +2722,9 @@ msgstr ""
msgid "Achievements|%{namespace_link} awarded you the %{bold_start}%{achievement_name}%{bold_end} achievement!"
msgstr ""
+msgid "Achievements|Achievements"
+msgstr ""
+
msgid "Achievements|Awarded %{timeAgo} by %{namespace}"
msgstr ""
@@ -18594,6 +18597,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -20329,6 +20335,9 @@ msgstr ""
msgid "Expand AI-generated summary"
msgstr ""
+msgid "Expand Readme"
+msgstr ""
+
msgid "Expand all"
msgstr ""
@@ -53953,6 +53962,9 @@ msgstr ""
msgid "UserProfile|%{id} ยท created %{created} by %{author}"
msgstr ""
+msgid "UserProfile|About"
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -53977,6 +53989,9 @@ msgstr ""
msgid "UserProfile|Busy"
msgstr ""
+msgid "UserProfile|Contact"
+msgstr ""
+
msgid "UserProfile|Contributed projects"
msgstr ""
@@ -54007,10 +54022,10 @@ msgstr ""
msgid "UserProfile|Groups are the best way to manage projects and members."
msgstr ""
-msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgid "UserProfile|Info"
msgstr ""
-msgid "UserProfile|Most Recent Activity"
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
msgstr ""
msgid "UserProfile|No snippets found."
@@ -54022,7 +54037,10 @@ msgstr ""
msgid "UserProfile|Personal projects"
msgstr ""
-msgid "UserProfile|Pronounced as: %{pronunciation}"
+msgid "UserProfile|Pronounced as: %{div_start}%{pronunciation}%{div_end}"
+msgstr ""
+
+msgid "UserProfile|Pronouns: %{div_start}%{pronouns}%{div_end}"
msgstr ""
msgid "UserProfile|Retry"
@@ -54085,9 +54103,15 @@ msgstr ""
msgid "UserProfile|User profile navigation"
msgstr ""
+msgid "UserProfile|User profile picture"
+msgstr ""
+
msgid "UserProfile|View all"
msgstr ""
+msgid "UserProfile|View large avatar"
+msgstr ""
+
msgid "UserProfile|View user in admin area"
msgstr ""
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 291c40f0f6b382888302814fdec6767e5615411e..c750cd0a8448190686ebce3efb94bab62587aabc 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -224,24 +224,6 @@ def selected_day_activities(visible: true)
end
end
- describe 'on smaller screens' do
- shared_examples 'hidden activity calendar' do
- include_context 'when user page is visited'
-
- it 'hides the activity calender' do
- expect(find('#js-overview')).not_to have_css('.js-contrib-calendar')
- end
- end
-
- context 'when screen size is xs' do
- before do
- resize_screen_xs
- end
-
- it_behaves_like 'hidden activity calendar'
- end
- end
-
describe 'first_day_of_week setting' do
context 'when first day of the week is set to Monday' do
before do
@@ -356,24 +338,6 @@ def selected_day_activities(visible: true)
end
end
- describe 'on smaller screens' do
- shared_examples 'hidden activity calendar' do
- include_context 'when user page is visited'
-
- it 'hides the activity calender' do
- expect(page).not_to have_css('[data-testid="contrib-calendar"]')
- end
- end
-
- context 'when screen size is xs' do
- before do
- resize_screen_xs
- end
-
- it_behaves_like 'hidden activity calendar'
- end
- end
-
describe 'first_day_of_week setting' do
context 'when first day of the week is set to Monday' do
before do
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 7e4308106be6359a401a284c6690b533d4336e9f..08d9ec5f3a9f364b681f8d407323c3f064ff5142 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -51,14 +51,14 @@
update_username(new_username)
visit new_user_path
expect(page).to have_current_path(new_user_path, ignore_query: true)
- expect(find('.user-info')).to have_content(new_username)
+ expect(find('.user-profile-header')).to have_content(new_username)
end
it 'the old user path redirects to the new path' do
update_username(new_username)
visit old_user_path
expect(page).to have_current_path(new_user_path, ignore_query: true)
- expect(find('.user-info')).to have_content(new_username)
+ expect(find('.user-profile-header')).to have_content(new_username)
end
context 'with a project' do
diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb
index 7edfb542594ffe8369d8bc3a49db7c62aa4c14bb..ac5682991b31cb5d823e12b921c388a7110fd7cf 100644
--- a/spec/features/profiles/user_visits_profile_spec.rb
+++ b/spec/features/profiles/user_visits_profile_spec.rb
@@ -48,7 +48,7 @@
it 'shows expected content', :js do
visit(user_path(user))
- page.within ".cover-block" do
+ page.within ".user-profile-header" do
expect(page).to have_content user.name
expect(page).to have_content user.username
end
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index 88d2d1c46bc174db7f2a7b253f79a0f485c67ead..58fb8faf05e09aa4265095cd8223909400f01990 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -51,7 +51,7 @@
visit user_path(user)
- expect(page).to have_selector(%(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png?width=96"]))
+ expect(page).to have_selector(%(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png?width=64"]))
# Cheating here to verify something that isn't user-facing, but is important
expect(user.reload.avatar.file).to exist
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index 1da61ecb86861d4296d74308fe57ca18092ecbe8..727b87c69dd8cea5ec35efe2093b0f82ea49053c 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -86,22 +86,6 @@ def push_code_contribution
end
describe 'projects section' do
- describe 'user has no personal projects' do
- include_context 'visit overview tab'
-
- it 'shows an empty project list with an info message' do
- page.within('.projects-block') do
- expect(page).to have_selector('.loading', visible: false)
- expect(page).to have_content('You haven\'t created any personal projects.')
- expect(page).not_to have_selector('.project-row')
- end
- end
-
- it 'does not show a link to the project list' do
- expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: false)
- end
- end
-
describe 'user has a personal project' do
before do
create(:project, :private, namespace: user.namespace, creator: user) { |p| p.add_maintainer(user) }
@@ -111,7 +95,7 @@ def push_code_contribution
it 'shows one entry in the list of projects' do
page.within('.projects-block') do
- expect(page).to have_selector('.project-row', count: 1)
+ expect(page).to have_selector('.gl-card', count: 1)
end
end
@@ -119,9 +103,9 @@ def push_code_contribution
expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true)
end
- it 'shows projects in "compact mode"' do
+ it 'shows projects in "card mode"' do
page.within('#js-overview .projects-block') do
- expect(find('.js-projects-list-holder')).to have_selector('.compact')
+ expect(find('.js-projects-list-holder')).to have_css('.gl-card')
end
end
end
@@ -135,9 +119,9 @@ def push_code_contribution
include_context 'visit overview tab'
- it 'shows max. ten entries in the list of projects' do
+ it 'shows max. 3 entries in the list of projects' do
page.within('.projects-block') do
- expect(page).to have_selector('.project-row', count: 10)
+ expect(page).to have_selector('.gl-card', count: 3)
end
end
@@ -315,7 +299,7 @@ def push_code_contribution
end
it 'shows projects panel' do
- expect(page).to have_selector('.projects-block')
+ expect(page).not_to have_selector('.projects-block')
end
end
end
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 730c31df899f8ed454d604000f4c790d03787b4d..f0ca0715e145d6896b824d04b3d138f31d8dee54 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -13,7 +13,7 @@
end
it 'shows the RSS link with overflow menu', :js do
- page.within('.user-cover-block') do
+ page.within('.user-profile-header') do
find_by_testid('base-dropdown-toggle').click
end
@@ -27,7 +27,7 @@
end
it 'has an RSS without a feed token', :js do
- page.within('.user-cover-block') do
+ page.within('.user-profile-header') do
find_by_testid('base-dropdown-toggle').click
end
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index c56b261fe282c2be3c04e863974c30e02b6bb9ca..754de6ed67f280859bcc5ec96a4f7ce441ca55d1 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -12,7 +12,7 @@
it 'shows copy user id action in the dropdown', :js do
subject
- page.within('.user-cover-block') do
+ page.within('.cover-controls') do
find_by_testid('base-dropdown-toggle').click
end
@@ -305,7 +305,7 @@
end
it 'shows user name as blocked' do
- expect(page).to have_css(".cover-title", text: 'Blocked user')
+ expect(page).to have_css(".user-profile-header", text: 'Blocked user')
end
it 'shows no additional fields' do
@@ -343,7 +343,7 @@
end
it 'shows user name as unconfirmed' do
- expect(page).to have_css(".cover-title", text: 'Unconfirmed user')
+ expect(page).to have_css(".user-profile-header", text: 'Unconfirmed user')
end
it 'shows no tab' do
@@ -393,7 +393,7 @@
subject
- expect(page).to have_content("(they/them)")
+ expect(page).to have_content("Pronouns: they/them")
end
it 'shows the pronunctiation of the user if there was one' do
@@ -435,12 +435,6 @@
stub_feature_flags(profile_tabs_vue: false)
end
- it 'shows the most recent activity' do
- subject
-
- expect(page).to have_content('Most Recent Activity')
- end
-
context 'when external authorization is enabled' do
before do
enable_external_authorization_service_check
diff --git a/spec/frontend/profile/components/activity_calendar_spec.js b/spec/frontend/profile/components/activity_calendar_spec.js
index fb9dc7b22f73a49057427b600bb1a6634d1fb2f5..043e717538c2d5859c8618e79f62535f1945af63 100644
--- a/spec/frontend/profile/components/activity_calendar_spec.js
+++ b/spec/frontend/profile/components/activity_calendar_spec.js
@@ -1,5 +1,4 @@
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
-import * as GitLabUIUtils from '@gitlab/ui/dist/utils';
import ActivityCalendar from '~/profile/components/activity_calendar.vue';
import AjaxCache from '~/lib/utils/ajax_cache';
@@ -57,23 +56,6 @@ describe('ActivityCalendar', () => {
expect(findCalendar().exists()).toBe(true);
expect(wrapper.findByText(ActivityCalendar.i18n.calendarHint).exists()).toBe(true);
});
-
- describe('when window is resized', () => {
- it('re-renders the calendar', async () => {
- createComponent();
-
- await waitForPromises();
-
- mockSuccessfulApiRequest();
- window.innerWidth = 1200;
- window.dispatchEvent(new Event('resize'));
-
- await waitForPromises();
-
- expect(findCalendar().exists()).toBe(true);
- expect(AjaxCache.retrieve).toHaveBeenCalledTimes(2);
- });
- });
});
describe('when API request is not successful', () => {
@@ -105,16 +87,4 @@ describe('ActivityCalendar', () => {
});
});
});
-
- describe('when screen is extra small', () => {
- beforeEach(() => {
- GitLabUIUtils.GlBreakpointInstance.getBreakpointSize.mockReturnValueOnce('xs');
- });
-
- it('does not render the calendar', () => {
- createComponent();
-
- expect(findCalendar().exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/read_more_spec.js b/spec/frontend/read_more_spec.js
index 6b1acfef8f553de67bc7129a28fda9d1ebdd4ced..a4104c39e04b135a0fae10a18269d5e83b05260f 100644
--- a/spec/frontend/read_more_spec.js
+++ b/spec/frontend/read_more_spec.js
@@ -85,3 +85,41 @@ describe('Read more click-to-expand functionality', () => {
});
});
});
+
+describe('data-read-more-height defines when to show the read-more button', () => {
+ const findTrigger = () => document.querySelectorAll('.js-read-more-trigger');
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ it('if not set shows button all the time', () => {
+ setHTMLFixture(`
+
+
Occaecat voluptate exercitation aliqua et duis eiusmod mollit esse ea laborum amet consectetur officia culpa anim. Fugiat laboris eu irure deserunt excepteur laboris irure quis. Occaecat nostrud irure do officia ea laborum velit sunt. Aliqua incididunt non deserunt proident magna aliqua sunt laborum laborum eiusmod ullamco. Et elit commodo irure. Labore eu nisi proident.
+
+
+ `);
+
+ initReadMore();
+
+ expect(findTrigger().length).toBe(1);
+ });
+
+ it('if set hides button as threshold is met', () => {
+ setHTMLFixture(`
+
+
Occaecat voluptate exercitation aliqua et duis eiusmod mollit esse ea laborum amet consectetur officia culpa anim. Fugiat laboris eu irure deserunt excepteur laboris irure quis. Occaecat nostrud irure do officia ea laborum velit sunt. Aliqua incididunt non deserunt proident magna aliqua sunt laborum laborum eiusmod ullamco. Et elit commodo irure. Labore eu nisi proident.
+
+
+ `);
+
+ initReadMore();
+
+ expect(findTrigger().length).toBe(0);
+ });
+});
diff --git a/spec/views/shared/projects/_list.html.haml_spec.rb b/spec/views/shared/projects/_list.html.haml_spec.rb
index 1b6c4e00c97f51f348439a24ef403ed834ecd72e..ff295662e3c6e22731ae8f297aa08351901ef252 100644
--- a/spec/views/shared/projects/_list.html.haml_spec.rb
+++ b/spec/views/shared/projects/_list.html.haml_spec.rb
@@ -31,6 +31,20 @@
expect(rendered).not_to have_css('a.issues')
expect(rendered).not_to have_css('a.merge-requests')
end
+
+ it 'renders list in list view' do
+ expect(rendered).not_to have_css('.gl-new-card')
+ end
+ end
+
+ context 'with projects in card mode' do
+ let(:projects) { build_stubbed_list(:project, 1) }
+
+ it 'renders card mode when set to true' do
+ render template: 'shared/projects/_list', locals: { card_mode: true }
+
+ expect(rendered).to have_css('.gl-new-card')
+ end
end
context 'without projects' do
diff --git a/spec/views/shared/projects/_project_card.html.haml_spec.rb b/spec/views/shared/projects/_project_card.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f4e31176af0a522de4490fbc5c3ea0455d13d06
--- /dev/null
+++ b/spec/views/shared/projects/_project_card.html.haml_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'shared/projects/_project_card.html.haml', feature_category: :shared do
+ let(:project) { build(:project) }
+
+ before do
+ allow(view)
+ .to receive(:current_application_settings)
+ .and_return(Gitlab::CurrentSettings.current_application_settings)
+ allow(view).to receive(:can?).and_return(true)
+ end
+
+ it 'renders as a card component' do
+ render 'shared/projects/project_card', use_creator_avatar: true, project: project
+
+ expect(rendered).to have_selector('.gl-new-card')
+ end
+
+ it 'renders creator avatar if project has a creator' do
+ render 'shared/projects/project_card', use_creator_avatar: true, project: project
+
+ expect(rendered).to have_selector('img.gl-avatar')
+ end
+
+ it 'renders a generic avatar if project does not have a creator' do
+ project.creator = nil
+
+ render 'shared/projects/project_card', use_creator_avatar: true, project: project
+
+ expect(rendered).to have_selector('.gl-avatar-identicon')
+ end
+end