diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-03 21:08:55 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-03 21:08:55 +0300 |
commit | 8f9307985ea047abb5b8a7c6c56bb644e0b7c363 (patch) | |
tree | 474c91c280b903c345bd94f4842abf481c535656 /spec | |
parent | 3cda3d43aef1e92e2eedf7383122c6db9c61149f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
27 files changed, 586 insertions, 206 deletions
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb index aa92ff6be33..daf0f36c28b 100644 --- a/spec/controllers/profiles_controller_spec.rb +++ b/spec/controllers/profiles_controller_spec.rb @@ -126,6 +126,16 @@ RSpec.describe ProfilesController, :request_store do expect(user.reload.pronunciation).to eq(pronunciation) expect(response).to have_gitlab_http_status(:found) end + + it 'allows updating user specified Discord User ID', :aggregate_failures do + discord_user_id = '1234567890123456789' + sign_in(user) + + put :update, params: { user: { discord: discord_user_id } } + + expect(user.reload.discord).to eq(discord_user_id) + expect(response).to have_gitlab_http_status(:found) + end end describe 'GET audit_log' do diff --git a/spec/finders/ci/freeze_periods_finder_spec.rb b/spec/finders/ci/freeze_periods_finder_spec.rb index 6c58028a221..0aa73e698ed 100644 --- a/spec/finders/ci/freeze_periods_finder_spec.rb +++ b/spec/finders/ci/freeze_periods_finder_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Ci::FreezePeriodsFinder, feature_category: :release_orchestration project.add_developer(user) end - it_behaves_like 'returns nothing' + it_behaves_like 'returns freeze_periods ordered by created_at asc' end context 'when user is not a project member' do diff --git a/spec/frontend/api/groups_api_spec.js b/spec/frontend/api/groups_api_spec.js index e064ee9fec4..abd627dab10 100644 --- a/spec/frontend/api/groups_api_spec.js +++ b/spec/frontend/api/groups_api_spec.js @@ -3,7 +3,7 @@ import getGroupTransferLocationsResponse from 'test_fixtures/api/groups/transfer import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import axios from '~/lib/utils/axios_utils'; import { DEFAULT_PER_PAGE } from '~/api'; -import { updateGroup, getGroupTransferLocations } from '~/api/groups_api'; +import { updateGroup, getGroupTransferLocations, getGroupMembers } from '~/api/groups_api'; const mockApiVersion = 'v4'; const mockUrlRoot = '/gitlab'; @@ -68,4 +68,30 @@ describe('GroupsApi', () => { }); }); }); + + describe('getGroupMembers', () => { + it('requests members of a group', async () => { + const expectedUrl = `${mockUrlRoot}/api/${mockApiVersion}/groups/${mockGroupId}/members`; + + const response = [{ id: 0, username: 'root' }]; + + mock.onGet(expectedUrl).replyOnce(200, response); + + await expect(getGroupMembers(mockGroupId)).resolves.toMatchObject({ + data: response, + }); + }); + + it('requests inherited members of a group when requested', async () => { + const expectedUrl = `${mockUrlRoot}/api/${mockApiVersion}/groups/${mockGroupId}/members/all`; + + const response = [{ id: 0, username: 'root' }]; + + mock.onGet(expectedUrl).replyOnce(200, response); + + await expect(getGroupMembers(mockGroupId, true)).resolves.toMatchObject({ + data: response, + }); + }); + }); }); diff --git a/spec/frontend/api/projects_api_spec.js b/spec/frontend/api/projects_api_spec.js index 0a6392bdc5b..a6ae8b14ea9 100644 --- a/spec/frontend/api/projects_api_spec.js +++ b/spec/frontend/api/projects_api_spec.js @@ -125,4 +125,30 @@ describe('~/api/projects_api.js', () => { }); }); }); + + describe('getProjectMembers', () => { + it('requests members of a project', async () => { + const expectedUrl = `/api/v7/projects/1/members`; + + const response = [{ id: 0, username: 'root' }]; + + mock.onGet(expectedUrl).replyOnce(200, response); + + await expect(projectsApi.getProjectMembers(projectId)).resolves.toMatchObject({ + data: response, + }); + }); + + it('requests inherited members of a project when requested', async () => { + const expectedUrl = `/api/v7/projects/1/members/all`; + + const response = [{ id: 0, username: 'root' }]; + + mock.onGet(expectedUrl).replyOnce(200, response); + + await expect(projectsApi.getProjectMembers(projectId, true)).resolves.toMatchObject({ + data: response, + }); + }); + }); }); diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js index c73f5cf3043..debd10de118 100644 --- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js +++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js @@ -141,6 +141,16 @@ describe('Commit box pipeline mini graph', () => { expect(upstreamPipeline).toEqual(null); }); + it('should render the latest downstream pipeline only', async () => { + createComponent(downstreamHandler); + + await waitForPromises(); + + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + + expect(downstreamPipelines).toHaveLength(1); + }); + it('should pass the pipeline path prop for the counter badge', async () => { createComponent(downstreamHandler); diff --git a/spec/frontend/commit/mock_data.js b/spec/frontend/commit/mock_data.js index 0f168ca6b17..80f62d5bfd8 100644 --- a/spec/frontend/commit/mock_data.js +++ b/spec/frontend/commit/mock_data.js @@ -21,6 +21,48 @@ const downstream = { }, __typename: 'Pipeline', }, + { + id: 'gid://gitlab/Ci::Pipeline/611', + path: '/root/job-log-sections/-/pipelines/611', + project: { + id: 'gid://gitlab/Project/21', + name: 'job-log-sections', + __typename: 'Project', + }, + detailedStatus: { + id: 'success-611-611', + group: 'success', + icon: 'status_success', + label: 'passed', + __typename: 'DetailedStatus', + }, + sourceJob: { + id: 'gid://gitlab/Ci::Bridge/531', + retried: true, + }, + __typename: 'Pipeline', + }, + { + id: 'gid://gitlab/Ci::Pipeline/609', + path: '/root/job-log-sections/-/pipelines/609', + project: { + id: 'gid://gitlab/Project/21', + name: 'job-log-sections', + __typename: 'Project', + }, + detailedStatus: { + id: 'success-609-609', + group: 'success', + icon: 'status_success', + label: 'passed', + __typename: 'DetailedStatus', + }, + sourceJob: { + id: 'gid://gitlab/Ci::Bridge/530', + retried: true, + }, + __typename: 'Pipeline', + }, ], __typename: 'PipelineConnection', }; diff --git a/spec/frontend/issues/list/components/issue_card_time_info_spec.js b/spec/frontend/issues/list/components/issue_card_time_info_spec.js index b0d3a63a8cf..ab4d023ee39 100644 --- a/spec/frontend/issues/list/components/issue_card_time_info_spec.js +++ b/spec/frontend/issues/list/components/issue_card_time_info_spec.js @@ -1,7 +1,7 @@ import { GlIcon, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; -import { IssuableStatus } from '~/issues/constants'; +import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants'; import IssueCardTimeInfo from '~/issues/list/components/issue_card_time_info.vue'; describe('CE IssueCardTimeInfo component', () => { @@ -25,7 +25,7 @@ describe('CE IssueCardTimeInfo component', () => { const findDueDate = () => wrapper.find('[data-testid="issuable-due-date"]'); const mountComponent = ({ - state = IssuableStatus.Open, + state = STATUS_OPEN, dueDate = issue.dueDate, milestoneDueDate = issue.milestone.dueDate, milestoneStartDate = issue.milestone.startDate, @@ -102,7 +102,7 @@ describe('CE IssueCardTimeInfo component', () => { it('does not render in red', () => { wrapper = mountComponent({ dueDate: '2020-10-10', - state: IssuableStatus.Closed, + state: STATUS_CLOSED, }); expect(findDueDate().classes()).not.toContain('gl-text-red-500'); diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js index 7c1d643dc0f..acb04e0af56 100644 --- a/spec/frontend/issues/show/components/app_spec.js +++ b/spec/frontend/issues/show/components/app_spec.js @@ -6,7 +6,13 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; -import { IssuableStatus, IssuableStatusText, IssuableType } from '~/issues/constants'; +import { + IssuableStatusText, + IssuableType, + STATUS_CLOSED, + STATUS_OPEN, + STATUS_REOPENED, +} from '~/issues/constants'; import IssuableApp from '~/issues/show/components/app.vue'; import DescriptionComponent from '~/issues/show/components/description.vue'; import EditedComponent from '~/issues/show/components/edited.vue'; @@ -473,11 +479,11 @@ describe('Issuable output', () => { }); it.each` - issuableType | issuableStatus | statusIcon - ${IssuableType.Issue} | ${IssuableStatus.Open} | ${'issues'} - ${IssuableType.Issue} | ${IssuableStatus.Closed} | ${'issue-closed'} - ${IssuableType.Epic} | ${IssuableStatus.Open} | ${'epic'} - ${IssuableType.Epic} | ${IssuableStatus.Closed} | ${'epic-closed'} + issuableType | issuableStatus | statusIcon + ${IssuableType.Issue} | ${STATUS_OPEN} | ${'issues'} + ${IssuableType.Issue} | ${STATUS_CLOSED} | ${'issue-closed'} + ${IssuableType.Epic} | ${STATUS_OPEN} | ${'epic'} + ${IssuableType.Epic} | ${STATUS_CLOSED} | ${'epic-closed'} `( 'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus', async ({ issuableType, issuableStatus, statusIcon }) => { @@ -491,9 +497,9 @@ describe('Issuable output', () => { it.each` title | state - ${'shows with Open when status is opened'} | ${IssuableStatus.Open} - ${'shows with Closed when status is closed'} | ${IssuableStatus.Closed} - ${'shows with Open when status is reopened'} | ${IssuableStatus.Reopened} + ${'shows with Open when status is opened'} | ${STATUS_OPEN} + ${'shows with Closed when status is closed'} | ${STATUS_CLOSED} + ${'shows with Open when status is reopened'} | ${STATUS_REOPENED} `('$title', async ({ state }) => { wrapper.setProps({ issuableStatus: state }); diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js index 8a0fdcb50d0..43507f78a97 100644 --- a/spec/frontend/issues/show/components/header_actions_spec.js +++ b/spec/frontend/issues/show/components/header_actions_spec.js @@ -4,7 +4,7 @@ import { shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import { mockTracking } from 'helpers/tracking_helper'; import { createAlert, VARIANT_SUCCESS } from '~/flash'; -import { IssuableStatus, IssueType } from '~/issues/constants'; +import { IssueType, STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants'; import DeleteIssueModal from '~/issues/show/components/delete_issue_modal.vue'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import HeaderActions from '~/issues/show/components/header_actions.vue'; @@ -81,7 +81,7 @@ describe('HeaderActions component', () => { const mountComponent = ({ props = {}, - issueState = IssuableStatus.Open, + issueState = STATUS_OPEN, blockedByIssues = [], mutateResponse = {}, } = {}) => { @@ -123,9 +123,9 @@ describe('HeaderActions component', () => { `('when issue type is $issueType', ({ issueType }) => { describe('close/reopen button', () => { describe.each` - description | issueState | buttonText | newIssueState - ${`when the ${issueType} is open`} | ${IssuableStatus.Open} | ${`Close ${issueType}`} | ${ISSUE_STATE_EVENT_CLOSE} - ${`when the ${issueType} is closed`} | ${IssuableStatus.Closed} | ${`Reopen ${issueType}`} | ${ISSUE_STATE_EVENT_REOPEN} + description | issueState | buttonText | newIssueState + ${`when the ${issueType} is open`} | ${STATUS_OPEN} | ${`Close ${issueType}`} | ${ISSUE_STATE_EVENT_CLOSE} + ${`when the ${issueType} is closed`} | ${STATUS_CLOSED} | ${`Reopen ${issueType}`} | ${ISSUE_STATE_EVENT_REOPEN} `('$description', ({ issueState, buttonText, newIssueState }) => { beforeEach(() => { dispatchEventSpy = jest.spyOn(document, 'dispatchEvent'); diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js index b2d7a61f548..e352f9708e4 100644 --- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js +++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js @@ -149,7 +149,7 @@ describe('Timeline events form', () => { it('should show the number of selected tags, when more than one is selected', async () => { await selectTags(mockTags); - expect(findTagsListbox().props('toggleText')).toBe('2 tags'); + expect(findTagsListbox().props('toggleText')).toBe(`${mockTags.length} tags`); }); it('should be cleared when clear is triggered', async () => { diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js index 4ccfbe41836..fb9c0a93907 100644 --- a/spec/frontend/search/mock_data.js +++ b/spec/frontend/search/mock_data.js @@ -622,3 +622,34 @@ export const MOCK_RECEIVE_AGGREGATIONS_ERROR_MUTATION = [ type: types.RECEIVE_AGGREGATIONS_ERROR, }, ]; + +export const TEST_RAW_BUCKETS = [ + { key: 'Go', count: 350 }, + { key: 'C', count: 298 }, + { key: 'JavaScript', count: 128 }, + { key: 'YAML', count: 58 }, + { key: 'Text', count: 46 }, + { key: 'Markdown', count: 37 }, + { key: 'HTML', count: 34 }, + { key: 'Shell', count: 34 }, + { key: 'Makefile', count: 21 }, + { key: 'JSON', count: 15 }, +]; + +export const TEST_FILTER_DATA = { + header: 'Language', + scopes: { BLOBS: 'blobs' }, + filterParam: 'language', + filters: { + GO: { label: 'Go', value: 'Go', count: 350 }, + C: { label: 'C', value: 'C', count: 298 }, + JAVASCRIPT: { label: 'JavaScript', value: 'JavaScript', count: 128 }, + YAML: { label: 'YAML', value: 'YAML', count: 58 }, + TEXT: { label: 'Text', value: 'Text', count: 46 }, + MARKDOWN: { label: 'Markdown', value: 'Markdown', count: 37 }, + HTML: { label: 'HTML', value: 'HTML', count: 34 }, + SHELL: { label: 'Shell', value: 'Shell', count: 34 }, + MAKEFILE: { label: 'Makefile', value: 'Makefile', count: 21 }, + JSON: { label: 'JSON', value: 'JSON', count: 15 }, + }, +}; diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js index 6aa40b7a2be..760c83a1dde 100644 --- a/spec/frontend/search/sidebar/components/app_spec.js +++ b/spec/frontend/search/sidebar/components/app_spec.js @@ -5,6 +5,7 @@ import { MOCK_QUERY } from 'jest/search/mock_data'; import GlobalSearchSidebar from '~/search/sidebar/components/app.vue'; import ResultsFilters from '~/search/sidebar/components/results_filters.vue'; import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue'; +import LanguageFilter from '~/search/sidebar/components/language_filter.vue'; Vue.use(Vuex); @@ -35,53 +36,41 @@ describe('GlobalSearchSidebar', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - const findSidebarSection = () => wrapper.find('section'); const findFilters = () => wrapper.findComponent(ResultsFilters); const findSidebarNavigation = () => wrapper.findComponent(ScopeNavigation); + const findLanguageAggregation = () => wrapper.findComponent(LanguageFilter); describe('renders properly', () => { - describe('scope=projects', () => { + describe('always', () => { beforeEach(() => { - createComponent({ urlQuery: { ...MOCK_QUERY, scope: 'projects' } }); + createComponent({}); }); - - it('shows section', () => { + it(`shows section`, () => { expect(findSidebarSection().exists()).toBe(true); }); - - it("doesn't shows filters", () => { - expect(findFilters().exists()).toBe(false); - }); }); - describe('scope=merge_requests', () => { + describe.each` + scope | showFilters | ShowsLanguage + ${'issues'} | ${true} | ${false} + ${'merge_requests'} | ${true} | ${false} + ${'projects'} | ${false} | ${false} + ${'blobs'} | ${false} | ${true} + `('sidebar scope: $scope', ({ scope, showFilters, ShowsLanguage }) => { beforeEach(() => { - createComponent({ urlQuery: { ...MOCK_QUERY, scope: 'merge_requests' } }); + createComponent( + { urlQuery: { scope } }, + { searchBlobsLanguageAggregation: true, searchPageVerticalNav: true }, + ); }); - it('shows section', () => { - expect(findSidebarSection().exists()).toBe(true); + it(`${!showFilters ? "doesn't" : ''} shows filters`, () => { + expect(findFilters().exists()).toBe(showFilters); }); - it('shows filters', () => { - expect(findFilters().exists()).toBe(true); - }); - }); - - describe('scope=issues', () => { - beforeEach(() => { - createComponent({ urlQuery: MOCK_QUERY }); - }); - it('shows section', () => { - expect(findSidebarSection().exists()).toBe(true); - }); - - it('shows filters', () => { - expect(findFilters().exists()).toBe(true); + it(`${!ShowsLanguage ? "doesn't" : ''} shows language filters`, () => { + expect(findLanguageAggregation().exists()).toBe(ShowsLanguage); }); }); @@ -94,4 +83,22 @@ describe('GlobalSearchSidebar', () => { }); }); }); + + describe('when search_blobs_language_aggregation is enabled', () => { + beforeEach(() => { + createComponent({ urlQuery: { scope: 'blobs' } }, { searchBlobsLanguageAggregation: true }); + }); + it('shows the language filter', () => { + expect(findLanguageAggregation().exists()).toBe(true); + }); + }); + + describe('when search_blobs_language_aggregation is disabled', () => { + beforeEach(() => { + createComponent({ urlQuery: { scope: 'blobs' } }, { searchBlobsLanguageAggregation: false }); + }); + it('hides the language filter', () => { + expect(findLanguageAggregation().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js new file mode 100644 index 00000000000..82017754b23 --- /dev/null +++ b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js @@ -0,0 +1,85 @@ +import { GlFormCheckboxGroup, GlFormCheckbox } from '@gitlab/ui'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { MOCK_QUERY, MOCK_LANGUAGE_AGGREGATIONS_BUCKETS } from 'jest/search/mock_data'; +import CheckboxFilter from '~/search/sidebar/components/checkbox_filter.vue'; + +import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; +import { convertFiltersData } from '~/search/sidebar/utils'; + +Vue.use(Vuex); + +describe('CheckboxFilter', () => { + let wrapper; + + const actionSpies = { + setQuery: jest.fn(), + }; + + const defaultProps = { + filterData: convertFiltersData(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS), + }; + + const createComponent = () => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + }, + actions: actionSpies, + }); + + wrapper = shallowMountExtended(CheckboxFilter, { + store, + propsData: { + ...defaultProps, + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + const findFormCheckboxGroup = () => wrapper.findComponent(GlFormCheckboxGroup); + const findAllCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox); + const fintAllCheckboxLabels = () => wrapper.findAllByTestId('label'); + const fintAllCheckboxLabelCounts = () => wrapper.findAllByTestId('labelCount'); + + describe('Renders correctly', () => { + it('renders form', () => { + expect(findFormCheckboxGroup().exists()).toBe(true); + }); + + it('renders checkbox-filter', () => { + expect(findAllCheckboxes().exists()).toBe(true); + }); + + it('renders all checkbox-filter checkboxes', () => { + expect(findAllCheckboxes()).toHaveLength(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS.length); + }); + + it('renders correctly label for the element', () => { + expect(fintAllCheckboxLabels().at(0).text()).toBe(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS[0].key); + }); + + it('renders correctly count for the element', () => { + expect(fintAllCheckboxLabelCounts().at(0).text()).toBe( + MOCK_LANGUAGE_AGGREGATIONS_BUCKETS[0].count.toString(), + ); + }); + }); + + describe('actions', () => { + it('triggers setQuery', () => { + const filter = + defaultProps.filterData.filters[Object.keys(defaultProps.filterData.filters)[0]].value; + findFormCheckboxGroup().vm.$emit('input', filter); + + expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), { + key: languageFilterData.filterParam, + value: filter, + }); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/components/language_filters_spec.js b/spec/frontend/search/sidebar/components/language_filters_spec.js new file mode 100644 index 00000000000..e297d1c33b0 --- /dev/null +++ b/spec/frontend/search/sidebar/components/language_filters_spec.js @@ -0,0 +1,152 @@ +import { GlAlert, GlFormCheckbox, GlForm } from '@gitlab/ui'; +import Vue, { nextTick } from 'vue'; +import Vuex from 'vuex'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { + MOCK_QUERY, + MOCK_AGGREGATIONS, + MOCK_LANGUAGE_AGGREGATIONS_BUCKETS, +} from 'jest/search/mock_data'; +import LanguageFilter from '~/search/sidebar/components/language_filter.vue'; +import CheckboxFilter from '~/search/sidebar/components/checkbox_filter.vue'; +import { MAX_ITEM_LENGTH } from '~/search/sidebar/constants/language_filter_data'; + +Vue.use(Vuex); + +describe('GlobalSearchSidebarLanguageFilter', () => { + let wrapper; + + const actionSpies = { + fetchLanguageAggregation: jest.fn(), + applyQuery: jest.fn(), + }; + + const getterSpies = { + langugageAggregationBuckets: jest.fn(() => MOCK_LANGUAGE_AGGREGATIONS_BUCKETS), + }; + + const createComponent = (initialState) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + urlQuery: MOCK_QUERY, + aggregations: MOCK_AGGREGATIONS, + ...initialState, + }, + actions: actionSpies, + getters: getterSpies, + }); + + wrapper = shallowMountExtended(LanguageFilter, { + store, + stubs: { + CheckboxFilter, + }, + }); + }; + + const findForm = () => wrapper.findComponent(GlForm); + const findCheckboxFilter = () => wrapper.findComponent(CheckboxFilter); + const findApplyButton = () => wrapper.findByTestId('apply-button'); + const findShowMoreButton = () => wrapper.findByTestId('show-more-button'); + const findAlert = () => wrapper.findComponent(GlAlert); + const findAllCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox); + const findHasOverMax = () => wrapper.findByTestId('has-over-max-text'); + + describe('Renders correctly', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders form', () => { + expect(findForm().exists()).toBe(true); + }); + + it('renders checkbox-filter', () => { + expect(findCheckboxFilter().exists()).toBe(true); + }); + + it('renders all checkbox-filter checkboxes', () => { + // 11th checkbox is hidden + expect(findAllCheckboxes()).toHaveLength(10); + }); + + it('renders ApplyButton', () => { + expect(findApplyButton().exists()).toBe(true); + }); + + it('renders Show More button', () => { + expect(findShowMoreButton().exists()).toBe(true); + }); + + it("doesn't render Alert", () => { + expect(findAlert().exists()).toBe(false); + }); + }); + + describe('ApplyButton', () => { + describe('when sidebarDirty is false', () => { + beforeEach(() => { + createComponent({ sidebarDirty: false }); + }); + + it('disables the button', () => { + expect(findApplyButton().attributes('disabled')).toBe('true'); + }); + }); + + describe('when sidebarDirty is true', () => { + beforeEach(() => { + createComponent({ sidebarDirty: true }); + }); + + it('enables the button', () => { + expect(findApplyButton().attributes('disabled')).toBe(undefined); + }); + }); + }); + + describe('Show All button works', () => { + beforeEach(() => { + createComponent(); + }); + + it(`renders ${MAX_ITEM_LENGTH} amount of items`, async () => { + findShowMoreButton().vm.$emit('click'); + await nextTick(); + expect(findAllCheckboxes()).toHaveLength(MAX_ITEM_LENGTH); + }); + + it(`renders more then ${MAX_ITEM_LENGTH} text`, async () => { + findShowMoreButton().vm.$emit('click'); + await nextTick(); + expect(findHasOverMax().exists()).toBe(true); + }); + + it(`doesn't render show more button after click`, async () => { + findShowMoreButton().vm.$emit('click'); + await nextTick(); + expect(findShowMoreButton().exists()).toBe(false); + }); + }); + + describe('actions', () => { + beforeEach(() => { + createComponent({}); + }); + + it('uses getter langugageAggregationBuckets', () => { + expect(getterSpies.langugageAggregationBuckets).toHaveBeenCalled(); + }); + + it('uses action fetchLanguageAggregation', () => { + expect(actionSpies.fetchLanguageAggregation).toHaveBeenCalled(); + }); + + it('clicking ApplyButton calls applyQuery', () => { + findForm().vm.$emit('submit', { preventDefault: () => {} }); + + expect(actionSpies.applyQuery).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/utils_spec.js b/spec/frontend/search/sidebar/utils_spec.js new file mode 100644 index 00000000000..652d32c836e --- /dev/null +++ b/spec/frontend/search/sidebar/utils_spec.js @@ -0,0 +1,10 @@ +import { convertFiltersData } from '~/search/sidebar/utils'; +import { TEST_RAW_BUCKETS, TEST_FILTER_DATA } from '../mock_data'; + +describe('Global Search sidebar utils', () => { + describe('convertFiltersData', () => { + it('converts raw buckets to array', () => { + expect(convertFiltersData(TEST_RAW_BUCKETS)).toStrictEqual(TEST_FILTER_DATA); + }); + }); +}); diff --git a/spec/haml_lint/linter/documentation_links_spec.rb b/spec/haml_lint/linter/documentation_links_spec.rb index 380df49cde3..d47127d9661 100644 --- a/spec/haml_lint/linter/documentation_links_spec.rb +++ b/spec/haml_lint/linter/documentation_links_spec.rb @@ -6,7 +6,7 @@ require 'haml_lint/spec' require_relative '../../../haml_lint/linter/documentation_links' -RSpec.describe HamlLint::Linter::DocumentationLinks do +RSpec.describe HamlLint::Linter::DocumentationLinks, feature_category: :tooling do include_context 'linter' shared_examples 'link validation rules' do |link_pattern| @@ -95,11 +95,8 @@ RSpec.describe HamlLint::Linter::DocumentationLinks do end end - context 'help_page_path' do - it_behaves_like 'link validation rules', 'help_page_path' - end - - context 'help_page_url' do - it_behaves_like 'link validation rules', 'help_page_url' - end + it_behaves_like 'link validation rules', 'help_page_path' + it_behaves_like 'link validation rules', 'help_page_url' + it_behaves_like 'link validation rules', 'Rails.application.routes.url_helpers.help_page_url' + it_behaves_like 'link validation rules', 'Gitlab::Routing.url_helpers.help_page_url' end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f628651b4da..4e921fe1719 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -540,6 +540,23 @@ RSpec.describe ApplicationHelper do end end + describe '#profile_social_links' do + context 'when discord is set' do + let_it_be(:user) { build(:user) } + let(:discord) { discord_url(user) } + + it 'returns an empty string if discord is not set' do + expect(discord).to eq('') + end + + it 'returns discord url when discord id is set' do + user.discord = '1234567890123456789' + + expect(discord).to eq('https://discord.com/users/1234567890123456789') + end + end + end + describe '#gitlab_ui_form_for' do let_it_be(:user) { build(:user) } diff --git a/spec/lib/gitlab/background_migration/sanitize_confidential_todos_spec.rb b/spec/lib/gitlab/background_migration/sanitize_confidential_todos_spec.rb deleted file mode 100644 index a19a3760958..00000000000 --- a/spec/lib/gitlab/background_migration/sanitize_confidential_todos_spec.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::SanitizeConfidentialTodos, :migration, feature_category: :team_planning do - let!(:issue_type_id) { table(:work_item_types).find_by(base_type: 0).id } - - let(:todos) { table(:todos) } - let(:notes) { table(:notes) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:project_features) { table(:project_features) } - let(:users) { table(:users) } - let(:issues) { table(:issues) } - let(:members) { table(:members) } - let(:project_authorizations) { table(:project_authorizations) } - - let(:user) { users.create!(first_name: 'Test', last_name: 'User', email: 'test@user.com', projects_limit: 1) } - let(:project_namespace1) { namespaces.create!(path: 'pns1', name: 'pns1') } - let(:project_namespace2) { namespaces.create!(path: 'pns2', name: 'pns2') } - - let(:project1) do - projects.create!(namespace_id: project_namespace1.id, - project_namespace_id: project_namespace1.id, visibility_level: 20) - end - - let(:project2) do - projects.create!(namespace_id: project_namespace2.id, - project_namespace_id: project_namespace2.id) - end - - let(:issue1) do - issues.create!( - project_id: project1.id, namespace_id: project_namespace1.id, issue_type: 1, title: 'issue1', author_id: user.id, - work_item_type_id: issue_type_id - ) - end - - let(:issue2) do - issues.create!( - project_id: project2.id, namespace_id: project_namespace2.id, issue_type: 1, title: 'issue2', - work_item_type_id: issue_type_id - ) - end - - let(:public_note) { notes.create!(note: 'text', project_id: project1.id) } - - let(:confidential_note) do - notes.create!(note: 'text', project_id: project1.id, confidential: true, - noteable_id: issue1.id, noteable_type: 'Issue') - end - - let(:other_confidential_note) do - notes.create!(note: 'text', project_id: project2.id, confidential: true, - noteable_id: issue2.id, noteable_type: 'Issue') - end - - let(:common_params) { { user_id: user.id, author_id: user.id, action: 1, state: 'pending', target_type: 'Note' } } - let!(:ignored_todo1) { todos.create!(**common_params) } - let!(:ignored_todo2) { todos.create!(**common_params, target_id: public_note.id, note_id: public_note.id) } - let!(:valid_todo) { todos.create!(**common_params, target_id: confidential_note.id, note_id: confidential_note.id) } - let!(:invalid_todo) do - todos.create!(**common_params, target_id: other_confidential_note.id, note_id: other_confidential_note.id) - end - - describe '#perform' do - before do - project_features.create!(project_id: project1.id, issues_access_level: 20, pages_access_level: 20) - members.create!(state: 0, source_id: project1.id, source_type: 'Project', - type: 'ProjectMember', user_id: user.id, access_level: 50, notification_level: 0, - member_namespace_id: project_namespace1.id) - project_authorizations.create!(project_id: project1.id, user_id: user.id, access_level: 50) - end - - subject(:perform) do - described_class.new( - start_id: notes.minimum(:id), - end_id: notes.maximum(:id), - batch_table: :notes, - batch_column: :id, - sub_batch_size: 1, - pause_ms: 0, - connection: ApplicationRecord.connection - ).perform - end - - it 'deletes todos where user can not read its note and logs deletion', :aggregate_failures do - expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger| - expect(logger).to receive(:info).with( - hash_including( - message: "#{described_class.name} deleting invalid todo", - attributes: hash_including(invalid_todo.attributes.slice(:id, :user_id, :target_id, :target_type)) - ) - ).once - end - - expect { perform }.to change(todos, :count).by(-1) - - expect(todos.all).to match_array([ignored_todo1, ignored_todo2, valid_todo]) - end - end -end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 6cff39c1167..d6d0de63a3b 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1947,6 +1947,53 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen expect(reference.name).to be_a(String) expect(reference.target).to be_a(String) end + + it 'filters by pattern' do + refs = repository.list_refs([Gitlab::Git::TAG_REF_PREFIX]) + + refs.each do |reference| + expect(reference.name).to include(Gitlab::Git::TAG_REF_PREFIX) + end + end + + context 'with pointing_at_oids and peel_tags options' do + let(:commit_id) { mutable_repository.commit.id } + let!(:annotated_tag) { mutable_repository.add_tag('annotated-tag', user: user, target: commit_id, message: 'Tag message') } + let!(:lw_tag) { mutable_repository.add_tag('lw-tag', user: user, target: commit_id) } + + it 'filters by target OIDs' do + refs = mutable_repository.list_refs([Gitlab::Git::TAG_REF_PREFIX], pointing_at_oids: [commit_id]) + + expect(refs.length).to eq(2) + expect(refs).to contain_exactly( + Gitaly::ListRefsResponse::Reference.new( + name: "#{Gitlab::Git::TAG_REF_PREFIX}#{lw_tag.name}", + target: commit_id + ), + Gitaly::ListRefsResponse::Reference.new( + name: "#{Gitlab::Git::TAG_REF_PREFIX}#{annotated_tag.name}", + target: annotated_tag.id + ) + ) + end + + it 'returns peeled_target for annotated tags' do + refs = mutable_repository.list_refs([Gitlab::Git::TAG_REF_PREFIX], pointing_at_oids: [commit_id], peel_tags: true) + + expect(refs.length).to eq(2) + expect(refs).to contain_exactly( + Gitaly::ListRefsResponse::Reference.new( + name: "#{Gitlab::Git::TAG_REF_PREFIX}#{lw_tag.name}", + target: commit_id + ), + Gitaly::ListRefsResponse::Reference.new( + name: "#{Gitlab::Git::TAG_REF_PREFIX}#{annotated_tag.name}", + target: annotated_tag.id, + peeled_target: commit_id + ) + ) + end + end end describe '#refs_by_oid' do diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 14d5cef103b..09d8ea3cc0a 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::GitalyClient::RefService do +RSpec.describe Gitlab::GitalyClient::RefService, feature_category: :gitaly do let_it_be(:project) { create(:project, :repository, create_tag: 'test') } let(:storage_name) { project.repository_storage } @@ -390,10 +390,15 @@ RSpec.describe Gitlab::GitalyClient::RefService do end describe '#list_refs' do + let(:oid) { project.repository.commit.id } + it 'sends a list_refs message' do expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:list_refs) - .with(gitaly_request_with_params(patterns: ['refs/heads/']), kind_of(Hash)) + .with( + gitaly_request_with_params(patterns: ['refs/heads/'], pointing_at_oids: [], peel_tags: false), + kind_of(Hash) + ) .and_call_original client.list_refs @@ -407,6 +412,24 @@ RSpec.describe Gitlab::GitalyClient::RefService do client.list_refs([Gitlab::Git::TAG_REF_PREFIX]) end + + it 'accepts a pointing_at_oids argument' do + expect_any_instance_of(Gitaly::RefService::Stub) + .to receive(:list_refs) + .with(gitaly_request_with_params(pointing_at_oids: [oid]), kind_of(Hash)) + .and_call_original + + client.list_refs(pointing_at_oids: [oid]) + end + + it 'accepts a peel_tags argument' do + expect_any_instance_of(Gitaly::RefService::Stub) + .to receive(:list_refs) + .with(gitaly_request_with_params(peel_tags: true), kind_of(Hash)) + .and_call_original + + client.list_refs(peel_tags: true) + end end describe '#find_refs_by_oid' do diff --git a/spec/migrations/sanitize_confidential_note_todos_spec.rb b/spec/migrations/sanitize_confidential_note_todos_spec.rb deleted file mode 100644 index 142378e07e1..00000000000 --- a/spec/migrations/sanitize_confidential_note_todos_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -require_migration! - -RSpec.describe SanitizeConfidentialNoteTodos, feature_category: :team_planning do - let(:migration) { described_class::MIGRATION } - - describe '#up' do - it 'schedules a batched background migration' do - migrate! - - expect(migration).to have_scheduled_batched_migration( - table_name: :notes, - column_name: :id, - interval: described_class::DELAY_INTERVAL, - batch_size: described_class::BATCH_SIZE, - max_batch_size: described_class::MAX_BATCH_SIZE, - sub_batch_size: described_class::SUB_BATCH_SIZE - ) - end - end - - describe '#down' do - it 'deletes all batched migration records' do - migrate! - schema_migrate_down! - - expect(migration).not_to have_scheduled_batched_migration - end - end -end diff --git a/spec/models/user_detail_spec.rb b/spec/models/user_detail_spec.rb index 1893b6530a5..7d433896cf8 100644 --- a/spec/models/user_detail_spec.rb +++ b/spec/models/user_detail_spec.rb @@ -38,6 +38,27 @@ RSpec.describe UserDetail do it { is_expected.to validate_length_of(:skype).is_at_most(500) } end + describe '#discord' do + it { is_expected.to validate_length_of(:discord).is_at_most(500) } + + context 'when discord is set' do + let_it_be(:user_detail) { create(:user_detail) } + + it 'accepts a valid discord user id' do + user_detail.discord = '1234567890123456789' + + expect(user_detail).to be_valid + end + + it 'throws an error when other url format is wrong' do + user_detail.discord = '123456789' + + expect(user_detail).not_to be_valid + expect(user_detail.errors.full_messages).to match_array([_('Discord must contain only a discord user ID.')]) + end + end + end + describe '#location' do it { is_expected.to validate_length_of(:location).is_at_most(500) } end @@ -72,11 +93,12 @@ RSpec.describe UserDetail do let(:user_detail) do create(:user_detail, bio: 'bio', + discord: '1234567890123456789', linkedin: 'linkedin', - twitter: 'twitter', - skype: 'skype', location: 'location', organization: 'organization', + skype: 'skype', + twitter: 'twitter', website_url: 'https://example.com') end @@ -90,11 +112,12 @@ RSpec.describe UserDetail do end it_behaves_like 'prevents `nil` value', :bio + it_behaves_like 'prevents `nil` value', :discord it_behaves_like 'prevents `nil` value', :linkedin - it_behaves_like 'prevents `nil` value', :twitter - it_behaves_like 'prevents `nil` value', :skype it_behaves_like 'prevents `nil` value', :location it_behaves_like 'prevents `nil` value', :organization + it_behaves_like 'prevents `nil` value', :skype + it_behaves_like 'prevents `nil` value', :twitter it_behaves_like 'prevents `nil` value', :website_url end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5570eb36c81..7b5b8acdb66 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -102,6 +102,9 @@ RSpec.describe User, feature_category: :user_profile do it { is_expected.to delegate_method(:requires_credit_card_verification).to(:user_detail).allow_nil } it { is_expected.to delegate_method(:requires_credit_card_verification=).to(:user_detail).with_arguments(:args).allow_nil } + it { is_expected.to delegate_method(:discord).to(:user_detail).allow_nil } + it { is_expected.to delegate_method(:discord=).to(:user_detail).with_arguments(:args).allow_nil } + it { is_expected.to delegate_method(:linkedin).to(:user_detail).allow_nil } it { is_expected.to delegate_method(:linkedin=).to(:user_detail).with_arguments(:args).allow_nil } diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb index 90ff42998ae..1b177934ace 100644 --- a/spec/models/wiki_directory_spec.rb +++ b/spec/models/wiki_directory_spec.rb @@ -100,7 +100,7 @@ RSpec.describe WikiDirectory do describe '#to_partial_path' do it 'returns the relative path to the partial to be used' do - expect(directory.to_partial_path).to eq('../shared/wikis/wiki_directory') + expect(directory.to_partial_path).to eq('shared/wikis/wiki_directory') end end end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index fcb041aebe5..21da06a222f 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -912,7 +912,7 @@ RSpec.describe WikiPage do describe '#to_partial_path' do it 'returns the relative path to the partial to be used' do - expect(build_wiki_page(container).to_partial_path).to eq('../shared/wikis/wiki_page') + expect(build_wiki_page(container).to_partial_path).to eq('shared/wikis/wiki_page') end end diff --git a/spec/rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction_spec.rb b/spec/rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction_spec.rb index 3f5845f8f58..968de7d2160 100644 --- a/spec/rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction_spec.rb +++ b/spec/rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction_spec.rb @@ -3,7 +3,7 @@ require 'rubocop_spec_helper' require_relative '../../../../rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction' -RSpec.describe RuboCop::Cop::Migration::PreventSingleStatementWithDisableDdlTransaction do +RSpec.describe RuboCop::Cop::Migration::PreventSingleStatementWithDisableDdlTransaction, feature_category: :database do context 'when in migration' do before do allow(cop).to receive(:in_migration?).and_return(true) diff --git a/spec/views/shared/wikis/_sidebar.html.haml_spec.rb b/spec/views/shared/wikis/_sidebar.html.haml_spec.rb index 0e7b657a154..821112e12bc 100644 --- a/spec/views/shared/wikis/_sidebar.html.haml_spec.rb +++ b/spec/views/shared/wikis/_sidebar.html.haml_spec.rb @@ -52,8 +52,8 @@ RSpec.describe 'shared/wikis/_sidebar.html.haml' do before do assign(:sidebar_wiki_entries, create_list(:wiki_page, 3, wiki: wiki)) assign(:sidebar_limited, true) - stub_template "../shared/wikis/_wiki_pages.html.erb" => "Entries: <%= @sidebar_wiki_entries.size %>" - stub_template "../shared/wikis/_wiki_page.html.erb" => 'A WIKI PAGE' + stub_template "shared/wikis/_wiki_pages.html.erb" => "Entries: <%= @sidebar_wiki_entries.size %>" + stub_template "shared/wikis/_wiki_page.html.erb" => 'A WIKI PAGE' end it 'does not show an alert' do @@ -66,7 +66,7 @@ RSpec.describe 'shared/wikis/_sidebar.html.haml' do it 'renders the wiki content' do render - expect(rendered).to include('A WIKI PAGE' * 3) + expect(rendered).to include("A WIKI PAGE\n" * 3) expect(rendered).to have_link('View All Pages') end |