summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/registrations/experience_levels_controller_spec.rb159
-rw-r--r--spec/features/commit_spec.rb2
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb8
-rw-r--r--spec/features/registrations/experience_level_spec.rb44
-rw-r--r--spec/frontend/diffs/components/app_spec.js2
-rw-r--r--spec/frontend/diffs/store/actions_spec.js7
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js6
-rw-r--r--spec/frontend/milestones/stores/mutations_spec.js58
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js43
-rw-r--r--spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js176
-rw-r--r--spec/lib/gitlab/pagination/offset_pagination_spec.rb37
-rw-r--r--spec/models/user_spec.rb3
-rw-r--r--spec/requests/api/users_spec.rb91
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb6
-rw-r--r--spec/views/projects/diffs/_stats.html.haml_spec.rb58
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