diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-05 00:08:34 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-05 00:08:34 +0300 |
commit | 484a245a95e97ae97f558a3d242f599853eb6d3c (patch) | |
tree | b267e42f87c1944428ce14e8c87c1713772397da /spec | |
parent | 23dda8d4edb3c0efeb34586969ce0c64e385f936 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
17 files changed, 389 insertions, 594 deletions
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index f279af90aa3..a09c9d258dc 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -23,7 +23,6 @@ RSpec.describe 'Project issue boards', :js do project.add_maintainer(user2) sign_in(user) - stub_feature_flags(gl_avatar_for_all_user_avatars: false) set_cookie('sidebar_collapsed', 'true') end diff --git a/spec/features/issues/user_scrolls_to_deeplinked_note_spec.rb b/spec/features/issues/user_scrolls_to_deeplinked_note_spec.rb index 5aae5abaf10..1fa8f533869 100644 --- a/spec/features/issues/user_scrolls_to_deeplinked_note_spec.rb +++ b/spec/features/issues/user_scrolls_to_deeplinked_note_spec.rb @@ -10,7 +10,6 @@ RSpec.describe 'User scrolls to deep-linked note' do context 'on issue page', :js do it 'on comment' do - stub_feature_flags(gl_avatar_for_all_user_avatars: false) visit project_issue_path(project, issue, anchor: "note_#{comment_1.id}") wait_for_requests diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb index 8225fcbfd89..09ac71d8ae8 100644 --- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb +++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb @@ -25,7 +25,6 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do before do project.add_maintainer(user) sign_in user - stub_feature_flags(gl_avatar_for_all_user_avatars: false) set_cookie('sidebar_collapsed', 'true') end diff --git a/spec/features/projects/user_sorts_projects_spec.rb b/spec/features/projects/user_sorts_projects_spec.rb index 15f7dae502d..49b9052ed3c 100644 --- a/spec/features/projects/user_sorts_projects_spec.rb +++ b/spec/features/projects/user_sorts_projects_spec.rb @@ -24,7 +24,6 @@ RSpec.describe 'User sorts projects and order persists' do end it "is set on the group_canonical_path" do - stub_feature_flags(group_overview_tabs_vue: false) visit(group_canonical_path(group)) within '[data-testid=group_sort_by_dropdown]' do @@ -33,7 +32,6 @@ RSpec.describe 'User sorts projects and order persists' do end it "is set on the details_group_path" do - stub_feature_flags(group_overview_tabs_vue: false) visit(details_group_path(group)) within '[data-testid=group_sort_by_dropdown]' do @@ -42,7 +40,7 @@ RSpec.describe 'User sorts projects and order persists' do end end - context "from explore projects" do + context "from explore projects", :js do before do stub_feature_flags(gl_listbox_for_sort_dropdowns: false) sign_in(user) @@ -51,10 +49,10 @@ RSpec.describe 'User sorts projects and order persists' do first(:link, 'Updated date').click end - it_behaves_like "sort order persists across all views", 'Updated date', 'Updated date' + it_behaves_like "sort order persists across all views", 'Updated date', 'Updated' end - context 'from dashboard projects' do + context 'from dashboard projects', :js do before do stub_feature_flags(gl_listbox_for_sort_dropdowns: false) sign_in(user) @@ -68,31 +66,29 @@ RSpec.describe 'User sorts projects and order persists' do context 'from group homepage', :js do before do - stub_feature_flags(gl_listbox_for_sort_dropdowns: false) - stub_feature_flags(group_overview_tabs_vue: false) sign_in(user) visit(group_canonical_path(group)) within '[data-testid=group_sort_by_dropdown]' do find('button.gl-dropdown-toggle').click - first(:button, 'Last created').click + first(:button, 'Created').click + wait_for_requests end end - it_behaves_like "sort order persists across all views", "Created date", "Last created" + it_behaves_like "sort order persists across all views", "Created date", "Created" end context 'from group details', :js do before do - stub_feature_flags(gl_listbox_for_sort_dropdowns: false) - stub_feature_flags(group_overview_tabs_vue: false) sign_in(user) visit(details_group_path(group)) within '[data-testid=group_sort_by_dropdown]' do find('button.gl-dropdown-toggle').click - first(:button, 'Most stars').click + first(:button, 'Stars').click + wait_for_requests end end - it_behaves_like "sort order persists across all views", "Stars", "Most stars" + it_behaves_like "sort order persists across all views", "Stars", "Stars" end end diff --git a/spec/frontend/diffs/components/commit_item_spec.js b/spec/frontend/diffs/components/commit_item_spec.js index 440f169be86..75d55376d09 100644 --- a/spec/frontend/diffs/components/commit_item_spec.js +++ b/spec/frontend/diffs/components/commit_item_spec.js @@ -82,7 +82,7 @@ describe('diffs/components/commit_item', () => { const imgElement = avatarElement.find('img'); expect(avatarElement.attributes('href')).toBe(commit.author.web_url); - expect(imgElement.classes()).toContain('s32'); + expect(imgElement.classes()).toContain('gl-avatar-s32'); expect(imgElement.attributes('alt')).toBe(commit.author.name); expect(imgElement.attributes('src')).toBe(commit.author.avatar_url); }); diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js index 33d76a8571d..75f70bbf19d 100644 --- a/spec/frontend/groups/components/app_spec.js +++ b/spec/frontend/groups/components/app_spec.js @@ -440,6 +440,10 @@ describe('AppComponent', () => { expect(eventHub.$on).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function)); expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', expect.any(Function)); expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', expect.any(Function)); + expect(eventHub.$on).toHaveBeenCalledWith( + 'fetchFilteredAndSortedGroups', + expect.any(Function), + ); }); it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', async () => { @@ -468,6 +472,46 @@ describe('AppComponent', () => { expect(eventHub.$off).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function)); expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', expect.any(Function)); expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', expect.any(Function)); + expect(eventHub.$off).toHaveBeenCalledWith( + 'fetchFilteredAndSortedGroups', + expect.any(Function), + ); + }); + }); + + describe('when `fetchFilteredAndSortedGroups` event is emitted', () => { + const search = 'Foo bar'; + const sort = 'created_asc'; + const emitFetchFilteredAndSortedGroups = () => { + eventHub.$emit('fetchFilteredAndSortedGroups', { + filterGroupsBy: search, + sortBy: sort, + }); + }; + let setPaginationInfoSpy; + + beforeEach(() => { + setPaginationInfoSpy = jest.spyOn(GroupsStore.prototype, 'setPaginationInfo'); + createShallowComponent(); + }); + + it('renders loading icon', async () => { + emitFetchFilteredAndSortedGroups(); + await nextTick(); + + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + }); + + it('calls API with expected params', () => { + emitFetchFilteredAndSortedGroups(); + + expect(getGroupsSpy).toHaveBeenCalledWith(undefined, undefined, search, sort, undefined); + }); + + it('updates pagination', () => { + emitFetchFilteredAndSortedGroups(); + + expect(setPaginationInfoSpy).toHaveBeenCalled(); }); }); diff --git a/spec/frontend/groups/components/overview_tabs_spec.js b/spec/frontend/groups/components/overview_tabs_spec.js index 379f509db82..be352301f43 100644 --- a/spec/frontend/groups/components/overview_tabs_spec.js +++ b/spec/frontend/groups/components/overview_tabs_spec.js @@ -1,4 +1,4 @@ -import { GlTab } from '@gitlab/ui'; +import { GlSorting, GlSortingItem, GlTab } from '@gitlab/ui'; import { nextTick } from 'vue'; import { createLocalVue } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; @@ -9,25 +9,38 @@ import GroupFolderComponent from '~/groups/components/group_folder.vue'; import GroupsStore from '~/groups/store/groups_store'; import GroupsService from '~/groups/service/groups_service'; import { createRouter } from '~/groups/init_overview_tabs'; +import eventHub from '~/groups/event_hub'; import { ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED, + OVERVIEW_TABS_SORTING_ITEMS, } from '~/groups/constants'; import axios from '~/lib/utils/axios_utils'; const localVue = createLocalVue(); localVue.component('GroupFolder', GroupFolderComponent); const router = createRouter(); +const [SORTING_ITEM_NAME, , SORTING_ITEM_UPDATED] = OVERVIEW_TABS_SORTING_ITEMS; describe('OverviewTabs', () => { let wrapper; let axiosMock; - const endpoints = { - subgroups_and_projects: '/groups/foobar/-/children.json', - shared: '/groups/foobar/-/shared_projects.json', - archived: '/groups/foobar/-/children.json?archived=only', + const defaultProvide = { + endpoints: { + subgroups_and_projects: '/groups/foobar/-/children.json', + shared: '/groups/foobar/-/shared_projects.json', + archived: '/groups/foobar/-/children.json?archived=only', + }, + newSubgroupPath: '/groups/new', + newProjectPath: 'projects/new', + newSubgroupIllustration: '', + newProjectIllustration: '', + emptySubgroupIllustration: '', + canCreateSubgroups: false, + canCreateProjects: false, + initialSort: 'name_asc', }; const routerMock = { @@ -36,18 +49,13 @@ describe('OverviewTabs', () => { const createComponent = async ({ route = { name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, params: { group: 'foo/bar/baz' } }, + provide = {}, } = {}) => { wrapper = mountExtended(OverviewTabs, { router, provide: { - endpoints, - newSubgroupPath: '/groups/new', - newProjectPath: 'projects/new', - newSubgroupIllustration: '', - newProjectIllustration: '', - emptySubgroupIllustration: '', - canCreateSubgroups: false, - canCreateProjects: false, + ...defaultProvide, + ...provide, }, localVue, mocks: { $route: route, $router: routerMock }, @@ -81,7 +89,7 @@ describe('OverviewTabs', () => { expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({ action: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, store: new GroupsStore({ showSchemaMarkup: true }), - service: new GroupsService(endpoints[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]), + service: new GroupsService(defaultProvide.endpoints[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]), hideProjects: false, renderEmptyState: true, }); @@ -102,7 +110,7 @@ describe('OverviewTabs', () => { expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({ action: ACTIVE_TAB_SHARED, store: new GroupsStore(), - service: new GroupsService(endpoints[ACTIVE_TAB_SHARED]), + service: new GroupsService(defaultProvide.endpoints[ACTIVE_TAB_SHARED]), hideProjects: false, renderEmptyState: false, }); @@ -125,7 +133,7 @@ describe('OverviewTabs', () => { expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({ action: ACTIVE_TAB_ARCHIVED, store: new GroupsStore(), - service: new GroupsService(endpoints[ACTIVE_TAB_ARCHIVED]), + service: new GroupsService(defaultProvide.endpoints[ACTIVE_TAB_ARCHIVED]), hideProjects: false, renderEmptyState: false, }); @@ -197,4 +205,109 @@ describe('OverviewTabs', () => { expect(routerMock.push).toHaveBeenCalledWith(expectedRoute); }); }); + + describe('searching and sorting', () => { + const setup = async () => { + jest.spyOn(eventHub, '$emit'); + await createComponent(); + + // Click through tabs so they are all loaded + await findTab(OverviewTabs.i18n[ACTIVE_TAB_SHARED]).trigger('click'); + await findTab(OverviewTabs.i18n[ACTIVE_TAB_ARCHIVED]).trigger('click'); + await findTab(OverviewTabs.i18n[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]).trigger('click'); + }; + + const sharedAssertions = ({ search, sort }) => { + it('sets `lazy` prop to `true` for all of the non-active tabs so they are reloaded after sort or search is applied', () => { + expect(findTabPanels().at(0).vm.$attrs.lazy).toBe(false); + expect(findTabPanels().at(1).vm.$attrs.lazy).toBe(true); + expect(findTabPanels().at(2).vm.$attrs.lazy).toBe(true); + }); + + it('emits `fetchFilteredAndSortedGroups` event from `eventHub`', () => { + expect(eventHub.$emit).toHaveBeenCalledWith( + `${ACTIVE_TAB_SUBGROUPS_AND_PROJECTS}fetchFilteredAndSortedGroups`, + { + filterGroupsBy: search, + sortBy: sort, + }, + ); + }); + }; + + describe('when search is typed in', () => { + const search = 'Foo bar'; + + beforeEach(async () => { + await setup(); + await wrapper.findByPlaceholderText(OverviewTabs.i18n.searchPlaceholder).setValue(search); + }); + + it('updates query string with `filter` key', () => { + expect(routerMock.push).toHaveBeenCalledWith({ query: { filter: search } }); + }); + + sharedAssertions({ search, sort: defaultProvide.initialSort }); + }); + + describe('when sort is changed', () => { + beforeEach(async () => { + await setup(); + wrapper.findAllComponents(GlSortingItem).at(2).vm.$emit('click'); + await nextTick(); + }); + + it('updates query string with `sort` key', () => { + expect(routerMock.push).toHaveBeenCalledWith({ + query: { sort: SORTING_ITEM_UPDATED.asc }, + }); + }); + + sharedAssertions({ search: '', sort: SORTING_ITEM_UPDATED.asc }); + }); + + describe('when sort direction is changed', () => { + beforeEach(async () => { + await setup(); + await wrapper + .findByRole('button', { name: 'Sorting Direction: Ascending' }) + .trigger('click'); + }); + + it('updates query string with `sort` key', () => { + expect(routerMock.push).toHaveBeenCalledWith({ + query: { sort: SORTING_ITEM_NAME.desc }, + }); + }); + + sharedAssertions({ search: '', sort: SORTING_ITEM_NAME.desc }); + }); + + describe('when `filter` and `sort` query strings are set', () => { + beforeEach(async () => { + await createComponent({ + route: { + name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, + params: { group: 'foo/bar/baz' }, + query: { filter: 'Foo bar', sort: SORTING_ITEM_UPDATED.desc }, + }, + }); + }); + + it('sets value of search input', () => { + expect( + wrapper.findByPlaceholderText(OverviewTabs.i18n.searchPlaceholder).element.value, + ).toBe('Foo bar'); + }); + + it('sets sort dropdown', () => { + expect(wrapper.findComponent(GlSorting).props()).toMatchObject({ + text: SORTING_ITEM_UPDATED.label, + isAscending: false, + }); + + expect(wrapper.findAllComponents(GlSortingItem).at(2).vm.$attrs.active).toBe(true); + }); + }); + }); }); diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap index 01494cb6a24..6fe60f3c2e6 100644 --- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap +++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap @@ -7,7 +7,7 @@ exports[`Repository last commit component renders commit widget 1`] = ` <user-avatar-link-stub class="gl-my-2 gl-mr-4" imgalt="" - imgcssclasses="gl-mr-0!" + imgcssclasses="" imgsize="32" imgsrc="https://test.com" linkhref="/test" diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_new_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_new_spec.js deleted file mode 100644 index f87737ca86a..00000000000 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_new_spec.js +++ /dev/null @@ -1,134 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlAvatar, GlTooltip } from '@gitlab/ui'; -import defaultAvatarUrl from 'images/no_avatar.png'; -import { placeholderImage } from '~/lazy_loader'; -import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image_new.vue'; - -jest.mock('images/no_avatar.png', () => 'default-avatar-url'); - -const PROVIDED_PROPS = { - size: 32, - imgSrc: 'myavatarurl.com', - imgAlt: 'mydisplayname', - cssClasses: 'myextraavatarclass', - tooltipText: 'tooltip text', - tooltipPlacement: 'bottom', -}; - -describe('User Avatar Image Component', () => { - let wrapper; - - const findAvatar = () => wrapper.findComponent(GlAvatar); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('Initialization', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - }, - }); - }); - - it('should render `GlAvatar` and provide correct properties to it', () => { - expect(findAvatar().attributes('data-src')).toBe( - `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, - ); - expect(findAvatar().props()).toMatchObject({ - src: `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, - alt: PROVIDED_PROPS.imgAlt, - size: PROVIDED_PROPS.size, - }); - }); - - it('should add correct CSS classes', () => { - const classes = wrapper.findComponent(GlAvatar).classes(); - expect(classes).toContain(PROVIDED_PROPS.cssClasses); - expect(classes).not.toContain('lazy'); - }); - }); - - describe('Initialization when lazy', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - lazy: true, - }, - }); - }); - - it('should add lazy attributes', () => { - expect(findAvatar().classes()).toContain('lazy'); - expect(findAvatar().attributes()).toMatchObject({ - src: placeholderImage, - 'data-src': `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, - }); - }); - - it('should use maximum number when size is provided as an object', () => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - size: { default: 16, md: 64, lg: 24 }, - lazy: true, - }, - }); - - expect(findAvatar().attributes('data-src')).toBe(`${PROVIDED_PROPS.imgSrc}?width=${64}`); - }); - }); - - describe('Initialization without src', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - imgSrc: null, - }, - }); - }); - - it('should have default avatar image', () => { - expect(findAvatar().props('src')).toBe(`${defaultAvatarUrl}?width=${PROVIDED_PROPS.size}`); - }); - }); - - describe('Dynamic tooltip content', () => { - const slots = { - default: ['Action!'], - }; - - describe('when `tooltipText` is provided and no default slot', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { ...PROVIDED_PROPS }, - }); - }); - - it('renders the tooltip with `tooltipText` as content', () => { - expect(wrapper.findComponent(GlTooltip).text()).toBe(PROVIDED_PROPS.tooltipText); - }); - }); - - describe('when `tooltipText` and default slot is provided', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { ...PROVIDED_PROPS }, - slots, - }); - }); - - it('does not render `tooltipText` inside the tooltip', () => { - expect(wrapper.findComponent(GlTooltip).text()).not.toBe(PROVIDED_PROPS.tooltipText); - }); - - it('renders the content provided via default slot', () => { - expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]); - }); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_old_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_old_spec.js deleted file mode 100644 index 2c1be6ec47e..00000000000 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_old_spec.js +++ /dev/null @@ -1,127 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlTooltip } from '@gitlab/ui'; -import defaultAvatarUrl from 'images/no_avatar.png'; -import { placeholderImage } from '~/lazy_loader'; -import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image_old.vue'; - -jest.mock('images/no_avatar.png', () => 'default-avatar-url'); - -const PROVIDED_PROPS = { - size: 32, - imgSrc: 'myavatarurl.com', - imgAlt: 'mydisplayname', - cssClasses: 'myextraavatarclass', - tooltipText: 'tooltip text', - tooltipPlacement: 'bottom', -}; - -const DEFAULT_PROPS = { - size: 20, -}; - -describe('User Avatar Image Component', () => { - let wrapper; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('Initialization', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - }, - }); - }); - - it('should have <img> as a child element', () => { - const imageElement = wrapper.find('img'); - - expect(imageElement.exists()).toBe(true); - expect(imageElement.attributes('src')).toBe( - `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, - ); - expect(imageElement.attributes('data-src')).toBe( - `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, - ); - expect(imageElement.attributes('alt')).toBe(PROVIDED_PROPS.imgAlt); - }); - - it('should properly render img css', () => { - const classes = wrapper.find('img').classes(); - expect(classes).toEqual(['avatar', 's32', PROVIDED_PROPS.cssClasses]); - expect(classes).not.toContain('lazy'); - }); - }); - - describe('Initialization when lazy', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - lazy: true, - }, - }); - }); - - it('should add lazy attributes', () => { - const imageElement = wrapper.find('img'); - - expect(imageElement.classes()).toContain('lazy'); - expect(imageElement.attributes('src')).toBe(placeholderImage); - expect(imageElement.attributes('data-src')).toBe( - `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, - ); - }); - }); - - describe('Initialization without src', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage); - }); - - it('should have default avatar image', () => { - const imageElement = wrapper.find('img'); - - expect(imageElement.attributes('src')).toBe( - `${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`, - ); - }); - }); - - describe('Dynamic tooltip content', () => { - const slots = { - default: ['Action!'], - }; - - describe('when `tooltipText` is provided and no default slot', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { ...PROVIDED_PROPS }, - }); - }); - - it('renders the tooltip with `tooltipText` as content', () => { - expect(wrapper.findComponent(GlTooltip).text()).toBe(PROVIDED_PROPS.tooltipText); - }); - }); - - describe('when `tooltipText` and default slot is provided', () => { - beforeEach(() => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { ...PROVIDED_PROPS }, - slots, - }); - }); - - it('does not render `tooltipText` inside the tooltip', () => { - expect(wrapper.findComponent(GlTooltip).text()).not.toBe(PROVIDED_PROPS.tooltipText); - }); - - it('renders the content provided via default slot', () => { - expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]); - }); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js index 6ad2ef226c2..d63b13981ac 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js @@ -1,7 +1,10 @@ import { shallowMount } from '@vue/test-utils'; +import { GlAvatar, GlTooltip } from '@gitlab/ui'; +import defaultAvatarUrl from 'images/no_avatar.png'; +import { placeholderImage } from '~/lazy_loader'; import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; -import UserAvatarImageNew from '~/vue_shared/components/user_avatar/user_avatar_image_new.vue'; -import UserAvatarImageOld from '~/vue_shared/components/user_avatar/user_avatar_image_old.vue'; + +jest.mock('images/no_avatar.png', () => 'default-avatar-url'); const PROVIDED_PROPS = { size: 32, @@ -15,37 +18,117 @@ const PROVIDED_PROPS = { describe('User Avatar Image Component', () => { let wrapper; - const createWrapper = (props = {}, { glAvatarForAllUserAvatars } = {}) => { - wrapper = shallowMount(UserAvatarImage, { - propsData: { - ...PROVIDED_PROPS, - ...props, - }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars, - }, - }, - }); - }; + const findAvatar = () => wrapper.findComponent(GlAvatar); afterEach(() => { wrapper.destroy(); }); - describe.each([ - [false, true, true], - [true, false, true], - [true, true, true], - [false, false, false], - ])( - 'when glAvatarForAllUserAvatars=%s and enforceGlAvatar=%s', - (glAvatarForAllUserAvatars, enforceGlAvatar, isUsingNewVersion) => { - it(`will render ${isUsingNewVersion ? 'new' : 'old'} version`, () => { - createWrapper({ enforceGlAvatar }, { glAvatarForAllUserAvatars }); - expect(wrapper.findComponent(UserAvatarImageNew).exists()).toBe(isUsingNewVersion); - expect(wrapper.findComponent(UserAvatarImageOld).exists()).toBe(!isUsingNewVersion); - }); - }, - ); + describe('Initialization', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...PROVIDED_PROPS, + }, + }); + }); + + it('should render `GlAvatar` and provide correct properties to it', () => { + expect(findAvatar().attributes('data-src')).toBe( + `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, + ); + expect(findAvatar().props()).toMatchObject({ + src: `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, + alt: PROVIDED_PROPS.imgAlt, + size: PROVIDED_PROPS.size, + }); + }); + + it('should add correct CSS classes', () => { + const classes = wrapper.findComponent(GlAvatar).classes(); + expect(classes).toContain(PROVIDED_PROPS.cssClasses); + expect(classes).not.toContain('lazy'); + }); + }); + + describe('Initialization when lazy', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...PROVIDED_PROPS, + lazy: true, + }, + }); + }); + + it('should add lazy attributes', () => { + expect(findAvatar().classes()).toContain('lazy'); + expect(findAvatar().attributes()).toMatchObject({ + src: placeholderImage, + 'data-src': `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`, + }); + }); + + it('should use maximum number when size is provided as an object', () => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...PROVIDED_PROPS, + size: { default: 16, md: 64, lg: 24 }, + lazy: true, + }, + }); + + expect(findAvatar().attributes('data-src')).toBe(`${PROVIDED_PROPS.imgSrc}?width=${64}`); + }); + }); + + describe('Initialization without src', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { + ...PROVIDED_PROPS, + imgSrc: null, + }, + }); + }); + + it('should have default avatar image', () => { + expect(findAvatar().props('src')).toBe(`${defaultAvatarUrl}?width=${PROVIDED_PROPS.size}`); + }); + }); + + describe('Dynamic tooltip content', () => { + const slots = { + default: ['Action!'], + }; + + describe('when `tooltipText` is provided and no default slot', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { ...PROVIDED_PROPS }, + }); + }); + + it('renders the tooltip with `tooltipText` as content', () => { + expect(wrapper.findComponent(GlTooltip).text()).toBe(PROVIDED_PROPS.tooltipText); + }); + }); + + describe('when `tooltipText` and default slot is provided', () => { + beforeEach(() => { + wrapper = shallowMount(UserAvatarImage, { + propsData: { ...PROVIDED_PROPS }, + slots, + }); + }); + + it('does not render `tooltipText` inside the tooltip', () => { + expect(wrapper.findComponent(GlTooltip).text()).not.toBe(PROVIDED_PROPS.tooltipText); + }); + + it('renders the content provided via default slot', () => { + expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]); + }); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_new_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_new_spec.js deleted file mode 100644 index f485a14cfea..00000000000 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_new_spec.js +++ /dev/null @@ -1,103 +0,0 @@ -import { GlAvatarLink } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { TEST_HOST } from 'spec/test_constants'; -import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link_new.vue'; - -describe('User Avatar Link Component', () => { - let wrapper; - - const findUserName = () => wrapper.findByTestId('user-avatar-link-username'); - - const defaultProps = { - linkHref: `${TEST_HOST}/myavatarurl.com`, - imgSize: 32, - imgSrc: `${TEST_HOST}/myavatarurl.com`, - imgAlt: 'mydisplayname', - imgCssClasses: 'myextraavatarclass', - tooltipText: 'tooltip text', - tooltipPlacement: 'bottom', - username: 'username', - }; - - const createWrapper = (props, slots) => { - wrapper = shallowMountExtended(UserAvatarLink, { - propsData: { - ...defaultProps, - ...props, - ...slots, - }, - }); - }; - - beforeEach(() => { - createWrapper(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render GlLink with correct props', () => { - const link = wrapper.findComponent(GlAvatarLink); - expect(link.exists()).toBe(true); - expect(link.attributes('href')).toBe(defaultProps.linkHref); - }); - - it('should render UserAvatarImage and provide correct props to it', () => { - expect(wrapper.findComponent(UserAvatarImage).exists()).toBe(true); - expect(wrapper.findComponent(UserAvatarImage).props()).toEqual({ - cssClasses: defaultProps.imgCssClasses, - imgAlt: defaultProps.imgAlt, - imgSrc: defaultProps.imgSrc, - lazy: false, - size: defaultProps.imgSize, - tooltipPlacement: defaultProps.tooltipPlacement, - tooltipText: '', - enforceGlAvatar: false, - }); - }); - - describe('when username provided', () => { - beforeEach(() => { - createWrapper({ username: defaultProps.username }); - }); - - it('should render provided username', () => { - expect(findUserName().text()).toBe(defaultProps.username); - }); - - it('should provide the tooltip data for the username', () => { - expect(findUserName().attributes()).toEqual( - expect.objectContaining({ - title: defaultProps.tooltipText, - 'tooltip-placement': defaultProps.tooltipPlacement, - }), - ); - }); - }); - - describe('when username is NOT provided', () => { - beforeEach(() => { - createWrapper({ username: '' }); - }); - - it('should NOT render username', () => { - expect(findUserName().exists()).toBe(false); - }); - }); - - describe('avatar-badge slot', () => { - const badge = '<span>User badge</span>'; - - beforeEach(() => { - createWrapper(defaultProps, { - 'avatar-badge': badge, - }); - }); - - it('should render provided `avatar-badge` slot content', () => { - expect(wrapper.html()).toContain(badge); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_old_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_old_spec.js deleted file mode 100644 index cf7a1025dba..00000000000 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_old_spec.js +++ /dev/null @@ -1,103 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { TEST_HOST } from 'spec/test_constants'; -import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link_old.vue'; - -describe('User Avatar Link Component', () => { - let wrapper; - - const findUserName = () => wrapper.find('[data-testid="user-avatar-link-username"]'); - - const defaultProps = { - linkHref: `${TEST_HOST}/myavatarurl.com`, - imgSize: 32, - imgSrc: `${TEST_HOST}/myavatarurl.com`, - imgAlt: 'mydisplayname', - imgCssClasses: 'myextraavatarclass', - tooltipText: 'tooltip text', - tooltipPlacement: 'bottom', - username: 'username', - }; - - const createWrapper = (props, slots) => { - wrapper = shallowMountExtended(UserAvatarLink, { - propsData: { - ...defaultProps, - ...props, - ...slots, - }, - }); - }; - - beforeEach(() => { - createWrapper(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render GlLink with correct props', () => { - const link = wrapper.findComponent(GlLink); - expect(link.exists()).toBe(true); - expect(link.attributes('href')).toBe(defaultProps.linkHref); - }); - - it('should render UserAvatarImage and povide correct props to it', () => { - expect(wrapper.findComponent(UserAvatarImage).exists()).toBe(true); - expect(wrapper.findComponent(UserAvatarImage).props()).toEqual({ - cssClasses: defaultProps.imgCssClasses, - imgAlt: defaultProps.imgAlt, - imgSrc: defaultProps.imgSrc, - lazy: false, - size: defaultProps.imgSize, - tooltipPlacement: defaultProps.tooltipPlacement, - tooltipText: '', - enforceGlAvatar: false, - }); - }); - - describe('when username provided', () => { - beforeEach(() => { - createWrapper({ username: defaultProps.username }); - }); - - it('should render provided username', () => { - expect(findUserName().text()).toBe(defaultProps.username); - }); - - it('should provide the tooltip data for the username', () => { - expect(findUserName().attributes()).toEqual( - expect.objectContaining({ - title: defaultProps.tooltipText, - 'tooltip-placement': defaultProps.tooltipPlacement, - }), - ); - }); - }); - - describe('when username is NOT provided', () => { - beforeEach(() => { - createWrapper({ username: '' }); - }); - - it('should NOT render username', () => { - expect(findUserName().exists()).toBe(false); - }); - }); - - describe('avatar-badge slot', () => { - const badge = '<span>User badge</span>'; - - beforeEach(() => { - createWrapper(defaultProps, { - 'avatar-badge': badge, - }); - }); - - it('should render provided `avatar-badge` slot content', () => { - expect(wrapper.html()).toContain(badge); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js index fd3f59008ec..df7ce449678 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js @@ -1,51 +1,102 @@ -import { shallowMount } from '@vue/test-utils'; +import { GlAvatarLink } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { TEST_HOST } from 'spec/test_constants'; +import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; -import UserAvatarLinkNew from '~/vue_shared/components/user_avatar/user_avatar_link_new.vue'; -import UserAvatarLinkOld from '~/vue_shared/components/user_avatar/user_avatar_link_old.vue'; - -const PROVIDED_PROPS = { - size: 32, - imgSrc: 'myavatarurl.com', - imgAlt: 'mydisplayname', - cssClasses: 'myextraavatarclass', - tooltipText: 'tooltip text', - tooltipPlacement: 'bottom', -}; describe('User Avatar Link Component', () => { let wrapper; - const createWrapper = (props = {}, { glAvatarForAllUserAvatars } = {}) => { - wrapper = shallowMount(UserAvatarLink, { + const findUserName = () => wrapper.findByTestId('user-avatar-link-username'); + + const defaultProps = { + linkHref: `${TEST_HOST}/myavatarurl.com`, + imgSize: 32, + imgSrc: `${TEST_HOST}/myavatarurl.com`, + imgAlt: 'mydisplayname', + imgCssClasses: 'myextraavatarclass', + tooltipText: 'tooltip text', + tooltipPlacement: 'bottom', + username: 'username', + }; + + const createWrapper = (props, slots) => { + wrapper = shallowMountExtended(UserAvatarLink, { propsData: { - ...PROVIDED_PROPS, + ...defaultProps, ...props, - }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars, - }, + ...slots, }, }); }; + beforeEach(() => { + createWrapper(); + }); + afterEach(() => { wrapper.destroy(); }); - describe.each([ - [false, true, true], - [true, false, true], - [true, true, true], - [false, false, false], - ])( - 'when glAvatarForAllUserAvatars=%s and enforceGlAvatar=%s', - (glAvatarForAllUserAvatars, enforceGlAvatar, isUsingNewVersion) => { - it(`will render ${isUsingNewVersion ? 'new' : 'old'} version`, () => { - createWrapper({ enforceGlAvatar }, { glAvatarForAllUserAvatars }); - expect(wrapper.findComponent(UserAvatarLinkNew).exists()).toBe(isUsingNewVersion); - expect(wrapper.findComponent(UserAvatarLinkOld).exists()).toBe(!isUsingNewVersion); + it('should render GlLink with correct props', () => { + const link = wrapper.findComponent(GlAvatarLink); + expect(link.exists()).toBe(true); + expect(link.attributes('href')).toBe(defaultProps.linkHref); + }); + + it('should render UserAvatarImage and provide correct props to it', () => { + expect(wrapper.findComponent(UserAvatarImage).exists()).toBe(true); + expect(wrapper.findComponent(UserAvatarImage).props()).toEqual({ + cssClasses: defaultProps.imgCssClasses, + imgAlt: defaultProps.imgAlt, + imgSrc: defaultProps.imgSrc, + lazy: false, + size: defaultProps.imgSize, + tooltipPlacement: defaultProps.tooltipPlacement, + tooltipText: '', + }); + }); + + describe('when username provided', () => { + beforeEach(() => { + createWrapper({ username: defaultProps.username }); + }); + + it('should render provided username', () => { + expect(findUserName().text()).toBe(defaultProps.username); + }); + + it('should provide the tooltip data for the username', () => { + expect(findUserName().attributes()).toEqual( + expect.objectContaining({ + title: defaultProps.tooltipText, + 'tooltip-placement': defaultProps.tooltipPlacement, + }), + ); + }); + }); + + describe('when username is NOT provided', () => { + beforeEach(() => { + createWrapper({ username: '' }); + }); + + it('should NOT render username', () => { + expect(findUserName().exists()).toBe(false); + }); + }); + + describe('avatar-badge slot', () => { + const badge = '<span>User badge</span>'; + + beforeEach(() => { + createWrapper(defaultProps, { + 'avatar-badge': badge, }); - }, - ); + }); + + it('should render provided `avatar-badge` slot content', () => { + expect(wrapper.html()).toContain(badge); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js index b9accbf0373..1ad6d043399 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js @@ -153,29 +153,4 @@ describe('UserAvatarList', () => { }); }); }); - - describe('additional styling for the image', () => { - it('should not add CSS class when feature flag `glAvatarForAllUserAvatars` is disabled', () => { - factory({ - propsData: { items: createList(1) }, - }); - - const link = wrapper.findComponent(UserAvatarLink); - expect(link.props('imgCssClasses')).not.toBe('gl-mr-3'); - }); - - it('should add CSS class when feature flag `glAvatarForAllUserAvatars` is enabled', () => { - factory({ - propsData: { items: createList(1) }, - provide: { - glFeatures: { - glAvatarForAllUserAvatars: true, - }, - }, - }); - - const link = wrapper.findComponent(UserAvatarLink); - expect(link.props('imgCssClasses')).toBe('gl-mr-3'); - }); - }); }); diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 0b6e51c26f5..a38483a956d 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -531,12 +531,14 @@ RSpec.describe GroupsHelper do describe '#group_overview_tabs_app_data' do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } + let_it_be(:initial_sort) { 'created_asc' } before do allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:can?).with(user, :create_subgroup, group) { true } allow(helper).to receive(:can?).with(user, :create_projects, group) { true } + allow(helper).to receive(:project_list_sort_by).and_return(initial_sort) end it 'returns expected hash' do @@ -545,7 +547,8 @@ RSpec.describe GroupsHelper do subgroups_and_projects_endpoint: including("/groups/#{group.path}/-/children.json"), shared_projects_endpoint: including("/groups/#{group.path}/-/shared_projects.json"), archived_projects_endpoint: including("/groups/#{group.path}/-/children.json?archived=only"), - current_group_visibility: group.visibility + current_group_visibility: group.visibility, + initial_sort: initial_sort }.merge(helper.group_overview_tabs_app_data(group)) ) end diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb index 749554f7786..e5c30769531 100644 --- a/spec/support/helpers/stub_gitlab_calls.rb +++ b/spec/support/helpers/stub_gitlab_calls.rb @@ -96,9 +96,9 @@ module StubGitlabCalls def stub_commonmark_sourcepos_disabled render_options = Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS - allow_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) - .to receive(:render_options) - .and_return(render_options) + allow_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| + allow(instance).to receive(:render_options).and_return(render_options) + end end private |