diff options
Diffstat (limited to 'spec')
15 files changed, 381 insertions, 319 deletions
diff --git a/spec/controllers/registrations/experience_levels_controller_spec.rb b/spec/controllers/registrations/experience_levels_controller_spec.rb deleted file mode 100644 index ad145264bb8..00000000000 --- a/spec/controllers/registrations/experience_levels_controller_spec.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Registrations::ExperienceLevelsController do - include AfterNextHelpers - - let_it_be(:namespace) { create(:group, path: 'group-path' ) } - let_it_be(:user) { create(:user) } - - let(:params) { { namespace_path: namespace.to_param } } - - describe 'GET #show' do - subject { get :show, params: params } - - context 'with an unauthenticated user' do - it { is_expected.to have_gitlab_http_status(:redirect) } - it { is_expected.to redirect_to(new_user_session_path) } - end - - context 'with an authenticated user' do - before do - sign_in(user) - end - - it { is_expected.to have_gitlab_http_status(:ok) } - it { is_expected.to render_template('layouts/minimal') } - it { is_expected.to render_template(:show) } - end - end - - describe 'PUT/PATCH #update' do - subject { patch :update, params: params } - - context 'with an unauthenticated user' do - it { is_expected.to have_gitlab_http_status(:redirect) } - it { is_expected.to redirect_to(new_user_session_path) } - end - - context 'with an authenticated user' do - let_it_be(:project) { build(:project, namespace: namespace, creator: user, path: 'project-path') } - let_it_be(:issues_board) { build(:board, id: 123, project: project) } - - before do - sign_in(user) - end - - context 'when user is successfully updated' do - context 'when no experience_level is sent' do - before do - user.user_preference.update_attribute(:experience_level, :novice) - end - - it 'will unset the user’s experience level' do - expect { subject }.to change { user.reload.experience_level }.to(nil) - end - end - - context 'when an expected experience level is sent' do - let(:params) { super().merge(experience_level: :novice) } - - it 'sets the user’s experience level' do - expect { subject }.to change { user.reload.experience_level }.from(nil).to('novice') - end - end - - context 'when an unexpected experience level is sent' do - let(:params) { super().merge(experience_level: :nonexistent) } - - it 'raises an exception' do - expect { subject }.to raise_error(ArgumentError, "'nonexistent' is not a valid experience_level") - end - end - - context 'when "Learn GitLab" project exists' do - let(:learn_gitlab_available?) { true } - - before do - allow_next_instance_of(LearnGitlab::Project) do |learn_gitlab| - allow(learn_gitlab).to receive(:available?).and_return(learn_gitlab_available?) - allow(learn_gitlab).to receive(:project).and_return(project) - allow(learn_gitlab).to receive(:board).and_return(issues_board) - allow(learn_gitlab).to receive(:label).and_return(double(id: 1)) - end - end - - context 'redirection' do - context 'when namespace_path param is missing' do - let(:params) { super().merge(namespace_path: nil) } - - where( - learn_gitlab_available?: [true, false] - ) - - with_them do - it { is_expected.to redirect_to('/') } - end - end - - context 'when we have a namespace_path param' do - using RSpec::Parameterized::TableSyntax - - where(:learn_gitlab_available?, :path) do - true | '/group-path/project-path/-/boards/123' - false | '/group-path' - end - - with_them do - it { is_expected.to redirect_to(path) } - end - end - end - - context 'when novice' do - let(:params) { super().merge(experience_level: :novice) } - - it 'adds a BoardLabel' do - expect_next(Boards::UpdateService).to receive(:execute) - - subject - end - end - - context 'when experienced' do - let(:params) { super().merge(experience_level: :experienced) } - - it 'does not add a BoardLabel' do - expect(Boards::UpdateService).not_to receive(:new) - - subject - end - end - end - - context 'when no "Learn GitLab" project exists' do - let(:params) { super().merge(experience_level: :novice) } - - before do - allow_next(LearnGitlab::Project).to receive(:available?).and_return(false) - end - - it 'does not add a BoardLabel' do - expect(Boards::UpdateService).not_to receive(:new) - - subject - end - end - end - - context 'when user update fails' do - before do - allow_any_instance_of(User).to receive(:save).and_return(false) - end - - it { is_expected.to render_template(:show) } - end - end - end -end diff --git a/spec/features/commit_spec.rb b/spec/features/commit_spec.rb index 80a30ab01b2..3fd613ce393 100644 --- a/spec/features/commit_spec.rb +++ b/spec/features/commit_spec.rb @@ -42,7 +42,7 @@ RSpec.describe 'Commit' do visit project_commit_path(project, commit) end - it "shows an adjusted count for changed files on this page" do + it "shows an adjusted count for changed files on this page", :js do expect(page).to have_content("Showing 1 changed file") end diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 76162fb800a..863fdbdadaa 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'User browses commits' do sign_in(user) end - it 'renders commit' do + it 'renders commit', :js do visit project_commit_path(project, sample_commit.id) expect(page).to have_content(sample_commit.message.gsub(/\s+/, ' ')) @@ -103,7 +103,7 @@ RSpec.describe 'User browses commits' do context 'when the blob does not exist' do let(:commit) { create(:commit, project: project) } - it 'renders successfully' do + it 'renders successfully', :js do allow_next_instance_of(Gitlab::Diff::File) do |instance| allow(instance).to receive(:blob).and_return(nil) end @@ -113,7 +113,9 @@ RSpec.describe 'User browses commits' do visit(project_commit_path(project, commit)) - expect(find('.diff-file-changes', visible: false)).to have_content('files/ruby/popen.rb') + click_button '2 changed files' + + expect(find('[data-testid="diff-stats-dropdown"]')).to have_content('files/ruby/popen.rb') end end diff --git a/spec/features/registrations/experience_level_spec.rb b/spec/features/registrations/experience_level_spec.rb deleted file mode 100644 index f432215d4a8..00000000000 --- a/spec/features/registrations/experience_level_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Experience level screen' do - let_it_be(:user) { create(:user, :unconfirmed) } - let_it_be(:group) { create(:group) } - - before do - group.add_owner(user) - gitlab_sign_in(user) - visit users_sign_up_experience_level_path(namespace_path: group.to_param) - end - - subject { page } - - it 'shows the intro content' do - is_expected.to have_content('Hello there') - is_expected.to have_content('Welcome to the guided GitLab tour') - is_expected.to have_content('What describes you best?') - end - - it 'shows the option for novice' do - is_expected.to have_content('Novice') - is_expected.to have_content('I’m not familiar with the basics of DevOps') - is_expected.to have_content('Show me the basics') - end - - it 'shows the option for experienced' do - is_expected.to have_content('Experienced') - is_expected.to have_content('I’m familiar with the basics of DevOps') - is_expected.to have_content('Show me advanced features') - end - - it 'does not display any flash messages' do - is_expected.not_to have_selector('.flash-container') - is_expected.not_to have_content("Please check your email (#{user.email}) to verify that you own this address and unlock the power of CI/CD") - end - - it 'does not include the footer links' do - is_expected.not_to have_link('Help') - is_expected.not_to have_link('About GitLab') - end -end diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index 1464dd84666..1a9e182ee2b 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -183,7 +183,7 @@ describe('diffs/components/app', () => { it('displays loading icon on batch loading', () => { createComponent({}, ({ state }) => { - state.diffs.isBatchLoading = true; + state.diffs.batchLoadingState = 'loading'; }); expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index 6d005b868a9..b35abc9da02 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -186,15 +186,16 @@ describe('DiffsStoreActions', () => { {}, { endpointBatch, diffViewType: 'inline' }, [ - { type: types.SET_BATCH_LOADING, payload: true }, + { type: types.SET_BATCH_LOADING_STATE, payload: 'loading' }, { type: types.SET_RETRIEVING_BATCHES, payload: true }, { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } }, - { type: types.SET_BATCH_LOADING, payload: false }, + { type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' }, { type: types.VIEW_DIFF_FILE, payload: 'test' }, { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } }, - { type: types.SET_BATCH_LOADING, payload: false }, + { type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' }, { type: types.VIEW_DIFF_FILE, payload: 'test2' }, { type: types.SET_RETRIEVING_BATCHES, payload: false }, + { type: types.SET_BATCH_LOADING_STATE, payload: 'error' }, ], [{ type: 'startRenderDiffsQueue' }, { type: 'startRenderDiffsQueue' }], done, diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js index b549ca42634..fc9ba223d5a 100644 --- a/spec/frontend/diffs/store/mutations_spec.js +++ b/spec/frontend/diffs/store/mutations_spec.js @@ -31,13 +31,13 @@ describe('DiffsStoreMutations', () => { }); }); - describe('SET_BATCH_LOADING', () => { + describe('SET_BATCH_LOADING_STATE', () => { it('should set loading state', () => { const state = {}; - mutations[types.SET_BATCH_LOADING](state, false); + mutations[types.SET_BATCH_LOADING_STATE](state, false); - expect(state.isBatchLoading).toEqual(false); + expect(state.batchLoadingState).toEqual(false); }); }); diff --git a/spec/frontend/milestones/stores/mutations_spec.js b/spec/frontend/milestones/stores/mutations_spec.js index 91b2acf23c5..a53d6ca5de1 100644 --- a/spec/frontend/milestones/stores/mutations_spec.js +++ b/spec/frontend/milestones/stores/mutations_spec.js @@ -174,6 +174,35 @@ describe('Milestones combobox Vuex store mutations', () => { }); }); + it('falls back to the length of list if pagination headers are missing', () => { + const response = { + data: [ + { + title: 'v0.1', + }, + { + title: 'v0.2', + }, + ], + headers: {}, + }; + + mutations[types.RECEIVE_PROJECT_MILESTONES_SUCCESS](state, response); + + expect(state.matches.projectMilestones).toEqual({ + list: [ + { + title: 'v0.1', + }, + { + title: 'v0.2', + }, + ], + error: null, + totalCount: 2, + }); + }); + describe(`${types.RECEIVE_PROJECT_MILESTONES_ERROR}`, () => { it('updates state.matches.projectMilestones to an empty state with the error object', () => { const error = new Error('Something went wrong!'); @@ -227,6 +256,35 @@ describe('Milestones combobox Vuex store mutations', () => { }); }); + it('falls back to the length of data received if pagination headers are missing', () => { + const response = { + data: [ + { + title: 'group-0.1', + }, + { + title: 'group-0.2', + }, + ], + headers: {}, + }; + + mutations[types.RECEIVE_GROUP_MILESTONES_SUCCESS](state, response); + + expect(state.matches.groupMilestones).toEqual({ + list: [ + { + title: 'group-0.1', + }, + { + title: 'group-0.2', + }, + ], + error: null, + totalCount: 2, + }); + }); + describe(`${types.RECEIVE_GROUP_MILESTONES_ERROR}`, () => { it('updates state.matches.groupMilestones to an empty state with the error object', () => { const error = new Error('Something went wrong!'); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js index 0504a42dfcf..e5147b0b6b1 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js @@ -5,6 +5,7 @@ import { mavenMetadata, nugetMetadata, packageData, + composerMetadata, } from 'jest/packages_and_registries/package_registry/mock_data'; import component from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue'; import { @@ -12,12 +13,15 @@ import { PACKAGE_TYPE_CONAN, PACKAGE_TYPE_MAVEN, PACKAGE_TYPE_NPM, + PACKAGE_TYPE_COMPOSER, } from '~/packages_and_registries/package_registry/constants'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; const mavenPackage = { packageType: PACKAGE_TYPE_MAVEN, metadata: mavenMetadata() }; const conanPackage = { packageType: PACKAGE_TYPE_CONAN, metadata: conanMetadata() }; const nugetPackage = { packageType: PACKAGE_TYPE_NUGET, metadata: nugetMetadata() }; +const composerPackage = { packageType: PACKAGE_TYPE_COMPOSER, metadata: composerMetadata() }; const npmPackage = { packageType: PACKAGE_TYPE_NPM, metadata: {} }; describe('Package Additional Metadata', () => { @@ -51,6 +55,9 @@ describe('Package Additional Metadata', () => { const findMavenApp = () => wrapper.findByTestId('maven-app'); const findMavenGroup = () => wrapper.findByTestId('maven-group'); const findElementLink = (container) => container.findComponent(GlLink); + const findComposerTargetSha = () => wrapper.findByTestId('composer-target-sha'); + const findComposerTargetShaCopyButton = () => wrapper.findComponent(ClipboardButton); + const findComposerJson = () => wrapper.findByTestId('composer-json'); it('has the correct title', () => { mountComponent(); @@ -62,11 +69,12 @@ describe('Package Additional Metadata', () => { }); it.each` - packageEntity | visible | packageType - ${mavenPackage} | ${true} | ${PACKAGE_TYPE_MAVEN} - ${conanPackage} | ${true} | ${PACKAGE_TYPE_CONAN} - ${nugetPackage} | ${true} | ${PACKAGE_TYPE_NUGET} - ${npmPackage} | ${false} | ${PACKAGE_TYPE_NPM} + packageEntity | visible | packageType + ${mavenPackage} | ${true} | ${PACKAGE_TYPE_MAVEN} + ${conanPackage} | ${true} | ${PACKAGE_TYPE_CONAN} + ${nugetPackage} | ${true} | ${PACKAGE_TYPE_NUGET} + ${composerPackage} | ${true} | ${PACKAGE_TYPE_COMPOSER} + ${npmPackage} | ${false} | ${PACKAGE_TYPE_NPM} `( `It is $visible that the component is visible when the package is $packageType`, ({ packageEntity, visible }) => { @@ -127,4 +135,29 @@ describe('Package Additional Metadata', () => { expect(element.props('icon')).toBe(icon); }); }); + + describe('composer metadata', () => { + beforeEach(() => { + mountComponent({ packageEntity: composerPackage }); + }); + + it.each` + name | finderFunction | text | icon + ${'target-sha'} | ${findComposerTargetSha} | ${'Target SHA: b83d6e391c22777fca1ed3012fce84f633d7fed0'} | ${'information-o'} + ${'composer-json'} | ${findComposerJson} | ${'Composer.json with license: MIT and version: 1.0.0'} | ${'information-o'} + `('$name element', ({ finderFunction, text, icon }) => { + const element = finderFunction(); + expect(element.exists()).toBe(true); + expect(element.text()).toBe(text); + expect(element.props('icon')).toBe(icon); + }); + + it('target-sha has a copy button', () => { + expect(findComposerTargetShaCopyButton().exists()).toBe(true); + expect(findComposerTargetShaCopyButton().props()).toMatchObject({ + text: 'b83d6e391c22777fca1ed3012fce84f633d7fed0', + title: 'Copy target SHA', + }); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js new file mode 100644 index 00000000000..04f63b4bd45 --- /dev/null +++ b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js @@ -0,0 +1,176 @@ +import { + GlSprintf, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, +} from '@gitlab/ui'; +import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import DiffStatsDropdown, { i18n } from '~/vue_shared/components/diff_stats_dropdown.vue'; + +jest.mock('fuzzaldrin-plus', () => ({ + filter: jest.fn().mockReturnValue([]), +})); + +const mockFiles = [ + { + added: 0, + href: '#a5cc2925ca8258af241be7e5b0381edf30266302', + icon: 'file-modified', + iconColor: '', + name: '', + path: '.gitignore', + removed: 3, + title: '.gitignore', + }, + { + added: 1, + href: '#fa288d1472d29beccb489a676f68739ad365fc47', + icon: 'file-modified', + iconColor: 'danger', + name: 'package-lock.json', + path: 'lock/file/path', + removed: 1, + }, +]; + +describe('Diff Stats Dropdown', () => { + let wrapper; + + const createComponent = ({ changed = 0, added = 0, deleted = 0, files = [] } = {}) => { + wrapper = shallowMountExtended(DiffStatsDropdown, { + propsData: { + changed, + added, + deleted, + files, + }, + stubs: { + GlSprintf, + GlDropdown, + }, + }); + }; + + const findChanged = () => wrapper.findComponent(GlDropdown); + const findChangedFiles = () => findChanged().findAllComponents(GlDropdownItem); + const findNoFilesText = () => findChanged().findComponent(GlDropdownText); + const findCollapsed = () => wrapper.findByTestId('diff-stats-additions-deletions-expanded'); + const findExpanded = () => wrapper.findByTestId('diff-stats-additions-deletions-collapsed'); + const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); + + describe('file item', () => { + beforeEach(() => { + createComponent({ files: mockFiles }); + }); + + it('when no file name provided ', () => { + expect(findChangedFiles().at(0).text()).toContain(i18n.noFileNameAvailable); + }); + + it('when all file data is available', () => { + const fileData = findChangedFiles().at(1); + const fileText = findChangedFiles().at(1).text(); + expect(fileText).toContain(mockFiles[1].name); + expect(fileText).toContain(mockFiles[1].path); + expect(fileData.props()).toMatchObject({ + iconName: mockFiles[1].icon, + iconColor: mockFiles[1].iconColor, + }); + }); + + it('when no files changed', () => { + createComponent({ files: [] }); + expect(findNoFilesText().text()).toContain(i18n.noFilesFound); + }); + }); + + describe.each` + changed | added | deleted | expectedDropdownHeader | expectedAddedDeletedExpanded | expectedAddedDeletedCollapsed + ${0} | ${0} | ${0} | ${'0 changed files'} | ${'+0 -0'} | ${'with 0 additions and 0 deletions'} + ${2} | ${0} | ${2} | ${'2 changed files'} | ${'+0 -2'} | ${'with 0 additions and 2 deletions'} + ${2} | ${2} | ${0} | ${'2 changed files'} | ${'+2 -0'} | ${'with 2 additions and 0 deletions'} + ${2} | ${1} | ${1} | ${'2 changed files'} | ${'+1 -1'} | ${'with 1 addition and 1 deletion'} + ${1} | ${0} | ${1} | ${'1 changed file'} | ${'+0 -1'} | ${'with 0 additions and 1 deletion'} + ${1} | ${1} | ${0} | ${'1 changed file'} | ${'+1 -0'} | ${'with 1 addition and 0 deletions'} + ${4} | ${2} | ${2} | ${'4 changed files'} | ${'+2 -2'} | ${'with 2 additions and 2 deletions'} + `( + 'when there are $changed changed file(s), $added added and $deleted deleted file(s)', + ({ + changed, + added, + deleted, + expectedDropdownHeader, + expectedAddedDeletedExpanded, + expectedAddedDeletedCollapsed, + }) => { + beforeAll(() => { + createComponent({ changed, added, deleted }); + }); + + afterAll(() => { + wrapper.destroy(); + }); + + it(`dropdown header should be '${expectedDropdownHeader}'`, () => { + expect(findChanged().props('text')).toBe(expectedDropdownHeader); + }); + + it(`added and deleted count in expanded section should be '${expectedAddedDeletedExpanded}'`, () => { + expect(findExpanded().text()).toBe(expectedAddedDeletedExpanded); + }); + + it(`added and deleted count in collapsed section should be '${expectedAddedDeletedCollapsed}'`, () => { + expect(findCollapsed().text()).toBe(expectedAddedDeletedCollapsed); + }); + }, + ); + + describe('fuzzy file search', () => { + beforeEach(() => { + createComponent({ files: mockFiles }); + }); + + it('should call `fuzzaldrinPlus.filter` to search for files when the search query is NOT empty', async () => { + const searchStr = 'file name'; + findSearchBox().vm.$emit('input', searchStr); + await nextTick(); + expect(fuzzaldrinPlus.filter).toHaveBeenCalledWith(mockFiles, searchStr, { key: 'name' }); + }); + + it('should NOT call `fuzzaldrinPlus.filter` to search for files when the search query is empty', async () => { + const searchStr = ''; + findSearchBox().vm.$emit('input', searchStr); + await nextTick(); + expect(fuzzaldrinPlus.filter).not.toHaveBeenCalled(); + }); + }); + + describe('selecting file dropdown item', () => { + beforeEach(() => { + createComponent({ files: mockFiles }); + }); + + it('updates the URL ', () => { + findChangedFiles().at(0).vm.$emit('click'); + expect(window.location.hash).toBe(mockFiles[0].href); + findChangedFiles().at(1).vm.$emit('click'); + expect(window.location.hash).toBe(mockFiles[1].href); + }); + }); + + describe('on dropdown open', () => { + beforeEach(() => { + createComponent(); + }); + + it('should set the search input focus', () => { + wrapper.vm.$refs.search.focusInput = jest.fn(); + findChanged().vm.$emit('shown'); + + expect(wrapper.vm.$refs.search.focusInput).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/lib/gitlab/pagination/offset_pagination_spec.rb b/spec/lib/gitlab/pagination/offset_pagination_spec.rb index f8d50fbc517..ffecbb06ff8 100644 --- a/spec/lib/gitlab/pagination/offset_pagination_spec.rb +++ b/spec/lib/gitlab/pagination/offset_pagination_spec.rb @@ -82,7 +82,7 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do context 'when the api_kaminari_count_with_limit feature flag is enabled' do before do - stub_feature_flags(api_kaminari_count_with_limit: true) + stub_feature_flags(api_kaminari_count_with_limit: true, lower_relation_max_count_limit: false) end context 'when resources count is less than MAX_COUNT_LIMIT' do @@ -120,6 +120,41 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do end end + context 'when lower_relation_max_count_limit FF is enabled' do + before do + stub_feature_flags(lower_relation_max_count_limit: true) + end + + it_behaves_like 'paginated response' + it_behaves_like 'response with pagination headers' + + context 'when limit is met' do + before do + stub_const("::Kaminari::ActiveRecordRelationMethods::MAX_COUNT_NEW_LOWER_LIMIT", 2) + end + + it_behaves_like 'paginated response' + + it 'does not return the X-Total and X-Total-Pages headers' do + expect_no_header('X-Total') + expect_no_header('X-Total-Pages') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '1') + expect_header('X-Next-Page', '2') + expect_header('X-Prev-Page', '') + + expect_header('Link', anything) do |_key, val| + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).not_to include('rel="last"') + expect(val).not_to include('rel="prev"') + end + + subject.paginate(resource) + end + end + end + it 'does not return the total headers when excluding them' do expect_no_header('X-Total') expect_no_header('X-Total-Pages') diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 50520f9bce4..4a83d62c401 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -65,9 +65,6 @@ RSpec.describe User do it { is_expected.to delegate_method(:render_whitespace_in_code).to(:user_preference) } it { is_expected.to delegate_method(:render_whitespace_in_code=).to(:user_preference).with_arguments(:args) } - it { is_expected.to delegate_method(:experience_level).to(:user_preference) } - it { is_expected.to delegate_method(:experience_level=).to(:user_preference).with_arguments(:args) } - it { is_expected.to delegate_method(:markdown_surround_selection).to(:user_preference) } it { is_expected.to delegate_method(:markdown_surround_selection=).to(:user_preference).with_arguments(:args) } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index bc88fdfcd5d..7d4f2b69fc7 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -14,6 +14,7 @@ RSpec.describe API::Users do let(:private_user) { create(:user, private_profile: true) } let(:deactivated_user) { create(:user, state: 'deactivated') } let(:banned_user) { create(:user, :banned) } + let(:internal_user) { create(:user, :bot) } context 'admin notes' do let_it_be(:admin) { create(:admin, note: '2019-10-06 | 2FA added | user requested | www.gitlab.com') } @@ -2869,53 +2870,73 @@ RSpec.describe API::Users do end end - describe 'POST /users/:id/block' do - let(:blocked_user) { create(:user, state: 'blocked') } + describe 'POST /users/:id/block', :aggregate_failures do + context 'when admin' do + subject(:block_user) { post api("/users/#{user_id}/block", admin) } - it 'blocks existing user' do - post api("/users/#{user.id}/block", admin) + context 'with an existing user' do + let(:user_id) { user.id } - aggregate_failures do - expect(response).to have_gitlab_http_status(:created) - expect(response.body).to eq('true') - expect(user.reload.state).to eq('blocked') + it 'blocks existing user' do + block_user + + expect(response).to have_gitlab_http_status(:created) + expect(response.body).to eq('true') + expect(user.reload.state).to eq('blocked') + end end - end - it 'does not re-block ldap blocked users' do - post api("/users/#{ldap_blocked_user.id}/block", admin) - expect(response).to have_gitlab_http_status(:forbidden) - expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') - end + context 'with an ldap blocked user' do + let(:user_id) { ldap_blocked_user.id } - it 'does not be available for non admin users' do - post api("/users/#{user.id}/block", user) - expect(response).to have_gitlab_http_status(:forbidden) - expect(user.reload.state).to eq('active') - end + it 'does not re-block ldap blocked users' do + block_user - it 'returns a 404 error if user id not found' do - post api('/users/0/block', admin) - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response['message']).to eq('404 User Not Found') - end + expect(response).to have_gitlab_http_status(:forbidden) + expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') + end + end - it 'returns a 403 error if user is internal' do - internal_user = create(:user, :bot) + context 'with a non existent user' do + let(:user_id) { non_existing_record_id } - post api("/users/#{internal_user.id}/block", admin) + it 'does not block non existent user, returns 404' do + block_user - expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('An internal user cannot be blocked') - end + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 User Not Found') + end + end - it 'returns a 201 if user is already blocked' do - post api("/users/#{blocked_user.id}/block", admin) + context 'with an internal user' do + let(:user_id) { internal_user.id } - aggregate_failures do - expect(response).to have_gitlab_http_status(:created) - expect(response.body).to eq('null') + it 'does not block internal user, returns 403' do + block_user + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('An internal user cannot be blocked') + end end + + context 'with a blocked user' do + let(:blocked_user) { create(:user, state: 'blocked') } + let(:user_id) { blocked_user.id } + + it 'returns a 201 if user is already blocked' do + block_user + + expect(response).to have_gitlab_http_status(:created) + expect(response.body).to eq('null') + end + end + end + + it 'is not available for non admin users' do + post api("/users/#{user.id}/block", user) + + expect(response).to have_gitlab_http_status(:forbidden) + expect(user.reload.state).to eq('active') end end diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb index 61feeff57bb..96df5a5f972 100644 --- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb @@ -157,7 +157,7 @@ RSpec.shared_examples 'User views a wiki page' do expect(page).to have_link('updated home', href: wiki_page_path(wiki, wiki_page, version_id: commit2, action: :diff)) end - it 'between the current and the previous version of a page' do + it 'between the current and the previous version of a page', :js do commit = wiki.commit visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) @@ -169,7 +169,7 @@ RSpec.shared_examples 'User views a wiki page' do expect_diff_links(commit) end - it 'between two old versions of a page' do + it 'between two old versions of a page', :js do wiki_page.update(message: 'latest home change', content: 'updated [another link](other-page)') # rubocop:disable Rails/SaveBang: commit = wiki.commit('HEAD^') visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) @@ -184,7 +184,7 @@ RSpec.shared_examples 'User views a wiki page' do expect_diff_links(commit) end - it 'for the oldest version of a page' do + it 'for the oldest version of a page', :js do commit = wiki.commit('HEAD^') visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) diff --git a/spec/views/projects/diffs/_stats.html.haml_spec.rb b/spec/views/projects/diffs/_stats.html.haml_spec.rb deleted file mode 100644 index f0580b50349..00000000000 --- a/spec/views/projects/diffs/_stats.html.haml_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'projects/diffs/_stats.html.haml' do - let(:project) { create(:project, :repository) } - let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } - - def render_view - render partial: "projects/diffs/stats", locals: { diff_files: commit.diffs.diff_files } - end - - context 'when the commit contains several changes' do - it 'uses plural for additions' do - render_view - - expect(rendered).to have_text('additions') - end - - it 'uses plural for deletions' do - render_view - end - end - - context 'when the commit contains no addition and no deletions' do - let(:commit) { project.commit('4cd80ccab63c82b4bad16faa5193fbd2aa06df40') } - - it 'uses plural for additions' do - render_view - - expect(rendered).to have_text('additions') - end - - it 'uses plural for deletions' do - render_view - - expect(rendered).to have_text('deletions') - end - end - - context 'when the commit contains exactly one addition and one deletion' do - let(:commit) { project.commit('08f22f255f082689c0d7d39d19205085311542bc') } - - it 'uses singular for additions' do - render_view - - expect(rendered).to have_text('addition') - expect(rendered).not_to have_text('additions') - end - - it 'uses singular for deletions' do - render_view - - expect(rendered).to have_text('deletion') - expect(rendered).not_to have_text('deletions') - end - end -end |