Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-07 15:09:00 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-07 15:09:00 +0300
commitba27dbddc7dbc42f2cc8d84e815a9ea19f87a81d (patch)
treee71fba864897fa78be7f0c40ded23d0f719abf84 /spec
parent708815aefead73a61473c1a611aea169ab16a358 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/users_controller_spec.rb2
-rw-r--r--spec/controllers/jira_connect/app_descriptor_controller_spec.rb17
-rw-r--r--spec/features/issues/user_bulk_edits_issues_spec.rb8
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb5
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js26
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js78
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js3
-rw-r--r--spec/frontend/notes/components/noteable_discussion_spec.js21
-rw-r--r--spec/frontend/sidebar/components/status/status_dropdown_spec.js61
-rw-r--r--spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js62
-rw-r--r--spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js23
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb6
-rw-r--r--spec/models/concerns/packages/downloadable_spec.rb19
-rw-r--r--spec/models/packages/npm/metadata_cache_spec.rb1
-rw-r--r--spec/models/packages/package_spec.rb14
-rw-r--r--spec/presenters/ml/candidate_details_presenter_spec.rb9
-rw-r--r--spec/requests/api/users_spec.rb4
-rw-r--r--spec/services/users/activate_service_spec.rb71
18 files changed, 302 insertions, 128 deletions
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index a53d200b1e1..399b7c02c52 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -356,7 +356,7 @@ RSpec.describe Admin::UsersController do
put :activate, params: { id: user.username }
user.reload
expect(user.active?).to be_falsey
- expect(flash[:notice]).to eq('Error occurred. A blocked user must be unblocked to be activated')
+ expect(flash[:alert]).to eq('Error occurred. A blocked user must be unblocked to be activated')
end
end
end
diff --git a/spec/controllers/jira_connect/app_descriptor_controller_spec.rb b/spec/controllers/jira_connect/app_descriptor_controller_spec.rb
index 48b315646de..3c9d495c33c 100644
--- a/spec/controllers/jira_connect/app_descriptor_controller_spec.rb
+++ b/spec/controllers/jira_connect/app_descriptor_controller_spec.rb
@@ -37,7 +37,8 @@ RSpec.describe JiraConnect::AppDescriptorController, feature_category: :integrat
url: 'https://gitlab.com'
},
links: {
- documentation: 'http://test.host/help/integration/jira_development_panel#gitlabcom-1'
+ documentation: 'http://test.host/help/integration/jira_development_panel#gitlabcom-1',
+ feedback: 'https://gitlab.com/gitlab-org/gitlab/-/issues/413652'
},
authentication: {
type: 'jwt'
@@ -90,5 +91,19 @@ RSpec.describe JiraConnect::AppDescriptorController, feature_category: :integrat
)
)
end
+
+ context 'when feature flag jira_for_cloud_app_feedback_link is disabled' do
+ before do
+ stub_feature_flags(jira_for_cloud_app_feedback_link: false)
+ end
+
+ it 'does not include the feedback link' do
+ get :show
+
+ expect(descriptor[:links]).not_to include(
+ feedback: 'https://gitlab.com/gitlab-org/gitlab/-/issues/413652'
+ )
+ end
+ end
end
end
diff --git a/spec/features/issues/user_bulk_edits_issues_spec.rb b/spec/features/issues/user_bulk_edits_issues_spec.rb
index 3e119d86c05..4c93a8e1c7a 100644
--- a/spec/features/issues/user_bulk_edits_issues_spec.rb
+++ b/spec/features/issues/user_bulk_edits_issues_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Multiple issue updating from issues#index', :js, feature_category: :team_planning do
+ include ListboxHelpers
+
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user) }
@@ -18,8 +20,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
click_button 'Bulk edit'
check 'Select all'
- click_button 'Select status'
- click_button 'Closed'
+ select_from_listbox('Closed', from: 'Select status')
click_update_issues_button
expect(page).to have_selector('.issue', count: 0)
@@ -31,8 +32,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
click_button 'Bulk edit'
check 'Select all'
- click_button 'Select status'
- click_button 'Open'
+ select_from_listbox('Open', from: 'Select status')
click_update_issues_button
expect(page).to have_selector('.issue', count: 0)
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index 45d57cf8374..2fa70b14957 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :code_review_workflow do
+ include ListboxHelpers
+
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:user2) { create(:user) }
@@ -110,8 +112,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod
def change_status(text)
click_button 'Bulk edit'
check 'Select all'
- click_button 'Select status'
- click_button text
+ select_from_listbox(text, from: 'Select status')
click_update_merge_requests_button
end
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js
index 8a39c5de2b3..53dbd796d85 100644
--- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js
@@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
import DetailRow from '~/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue';
describe('CandidateDetailRow', () => {
@@ -9,14 +8,14 @@ describe('CandidateDetailRow', () => {
let wrapper;
- const createWrapper = (href = '') => {
+ const createWrapper = ({ slots = {} } = {}) => {
wrapper = shallowMount(DetailRow, {
- propsData: { sectionLabel: 'Section', label: 'Item', text: 'Text', href },
+ propsData: { sectionLabel: 'Section', label: 'Item' },
+ slots,
});
};
const findCellAt = (index) => wrapper.findAll('td').at(index);
- const findLink = () => findCellAt(ROW_VALUE_CELL).findComponent(GlLink);
beforeEach(() => createWrapper());
@@ -28,22 +27,15 @@ describe('CandidateDetailRow', () => {
expect(findCellAt(ROW_LABEL_CELL).text()).toBe('Item');
});
- describe('No href', () => {
- it('Renders text', () => {
- expect(findCellAt(ROW_VALUE_CELL).text()).toBe('Text');
- });
-
- it('Does not render as link', () => {
- expect(findLink().exists()).toBe(false);
- });
+ it('renders nothing on item cell', () => {
+ expect(findCellAt(ROW_VALUE_CELL).text()).toBe('');
});
- describe('With href', () => {
- beforeEach(() => createWrapper('LINK'));
+ describe('With slot', () => {
+ beforeEach(() => createWrapper({ slots: { default: 'Some content' } }));
- it('Renders link', () => {
- expect(findLink().attributes().href).toBe('LINK');
- expect(findLink().text()).toBe('Text');
+ it('Renders slot', () => {
+ expect(findCellAt(ROW_VALUE_CELL).text()).toBe('Some content');
});
});
});
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
index 07d501c4e44..0b3b780cb3f 100644
--- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { GlAvatarLabeled, GlLink } from '@gitlab/ui';
import MlCandidatesShow from '~/ml/experiment_tracking/routes/candidates/show';
import DetailRow from '~/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue';
import { TITLE_LABEL } from '~/ml/experiment_tracking/routes/candidates/show/translations';
@@ -9,6 +10,7 @@ import { newCandidate } from './mock_data';
describe('MlCandidatesShow', () => {
let wrapper;
const CANDIDATE = newCandidate();
+ const USER_ROW = 6;
const createWrapper = (createCandidate = () => CANDIDATE) => {
wrapper = shallowMount(MlCandidatesShow, {
@@ -19,8 +21,12 @@ describe('MlCandidatesShow', () => {
const findDeleteButton = () => wrapper.findComponent(DeleteButton);
const findHeader = () => wrapper.findComponent(ModelExperimentsHeader);
const findNthDetailRow = (index) => wrapper.findAllComponents(DetailRow).at(index);
+ const findLinkInNthDetailRow = (index) => findNthDetailRow(index).findComponent(GlLink);
const findSectionLabel = (label) => wrapper.find(`[sectionLabel='${label}']`);
const findLabel = (label) => wrapper.find(`[label='${label}']`);
+ const findCiUserDetailRow = () => findNthDetailRow(USER_ROW);
+ const findCiUserAvatar = () => findCiUserDetailRow().findComponent(GlAvatarLabeled);
+ const findCiUserAvatarNameLink = () => findCiUserAvatar().findComponent(GlLink);
describe('Header', () => {
beforeEach(() => createWrapper());
@@ -42,36 +48,64 @@ describe('MlCandidatesShow', () => {
describe('All info available', () => {
beforeEach(() => createWrapper());
+ const mrText = `!${CANDIDATE.info.ci_job.merge_request.iid} ${CANDIDATE.info.ci_job.merge_request.title}`;
const expectedTable = [
- ['Info', 'ID', CANDIDATE.info.iid, ''],
- ['', 'MLflow run ID', CANDIDATE.info.eid, ''],
- ['', 'Status', CANDIDATE.info.status, ''],
- ['', 'Experiment', CANDIDATE.info.experiment_name, CANDIDATE.info.path_to_experiment],
- ['', 'Artifacts', 'Artifacts', CANDIDATE.info.path_to_artifact],
- ['CI', 'Job', CANDIDATE.info.ci_job.name, CANDIDATE.info.ci_job.path],
- ['', 'Triggered by', CANDIDATE.info.ci_job.user.username, CANDIDATE.info.ci_job.user.path],
- [
- '',
- 'Merge request',
- CANDIDATE.info.ci_job.merge_request.title,
- CANDIDATE.info.ci_job.merge_request.path,
- ],
- ['Parameters', CANDIDATE.params[0].name, CANDIDATE.params[0].value, ''],
- ['', CANDIDATE.params[1].name, CANDIDATE.params[1].value, ''],
- ['Metrics', CANDIDATE.metrics[0].name, CANDIDATE.metrics[0].value, ''],
- ['', CANDIDATE.metrics[1].name, CANDIDATE.metrics[1].value, ''],
- ['Metadata', CANDIDATE.metadata[0].name, CANDIDATE.metadata[0].value, ''],
- ['', CANDIDATE.metadata[1].name, CANDIDATE.metadata[1].value, ''],
+ ['Info', 'ID', CANDIDATE.info.iid],
+ ['', 'MLflow run ID', CANDIDATE.info.eid],
+ ['', 'Status', CANDIDATE.info.status],
+ ['', 'Experiment', CANDIDATE.info.experiment_name],
+ ['', 'Artifacts', 'Artifacts'],
+ ['CI', 'Job', CANDIDATE.info.ci_job.name],
+ ['', 'Triggered by', 'CI User'],
+ ['', 'Merge request', mrText],
+ ['Parameters', CANDIDATE.params[0].name, CANDIDATE.params[0].value],
+ ['', CANDIDATE.params[1].name, CANDIDATE.params[1].value],
+ ['Metrics', CANDIDATE.metrics[0].name, CANDIDATE.metrics[0].value],
+ ['', CANDIDATE.metrics[1].name, CANDIDATE.metrics[1].value],
+ ['Metadata', CANDIDATE.metadata[0].name, CANDIDATE.metadata[0].value],
+ ['', CANDIDATE.metadata[1].name, CANDIDATE.metadata[1].value],
].map((row, index) => [index, ...row]);
it.each(expectedTable)(
'row %s is created correctly',
- (index, sectionLabel, label, text, href) => {
- const row = findNthDetailRow(index);
+ (rowIndex, sectionLabel, label, text) => {
+ const row = findNthDetailRow(rowIndex);
- expect(row.props()).toMatchObject({ sectionLabel, label, text, href });
+ expect(row.props()).toMatchObject({ sectionLabel, label });
+ expect(row.text()).toBe(text);
},
);
+
+ describe('Table links', () => {
+ const linkRows = [
+ [3, CANDIDATE.info.path_to_experiment],
+ [4, CANDIDATE.info.path_to_artifact],
+ [5, CANDIDATE.info.ci_job.path],
+ [7, CANDIDATE.info.ci_job.merge_request.path],
+ ];
+
+ it.each(linkRows)('row %s is created correctly', (rowIndex, href) => {
+ expect(findLinkInNthDetailRow(rowIndex).attributes().href).toBe(href);
+ });
+ });
+
+ describe('CI triggerer', () => {
+ it('renders user row', () => {
+ const avatar = findCiUserAvatar();
+ expect(avatar.props()).toMatchObject({
+ label: '',
+ });
+ expect(avatar.attributes().src).toEqual('/img.png');
+ });
+
+ it('renders user name', () => {
+ const nameLink = findCiUserAvatarNameLink();
+
+ expect(nameLink.attributes().href).toEqual('path/to/ci/user');
+ expect(nameLink.text()).toEqual('CI User');
+ });
+ });
+
it('does not render params', () => {
expect(findSectionLabel('Parameters').exists()).toBe(true);
});
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js
index 16c1b29f7bc..3fbcf122997 100644
--- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js
@@ -24,11 +24,14 @@ export const newCandidate = () => ({
path: 'path/to/job',
merge_request: {
path: 'path/to/mr',
+ iid: 1,
title: 'Some MR',
},
user: {
path: 'path/to/ci/user',
+ name: 'CI User',
username: 'ciuser',
+ avatar: '/img.png',
},
},
},
diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js
index 0ed7069a98d..fa3a1979a98 100644
--- a/spec/frontend/notes/components/noteable_discussion_spec.js
+++ b/spec/frontend/notes/components/noteable_discussion_spec.js
@@ -49,14 +49,14 @@ describe('noteable_discussion component', () => {
});
};
- const createComponent = ({ storeMock = createStore() } = {}) => {
+ const createComponent = ({ storeMock = createStore(), discussion = discussionMock } = {}) => {
store = storeMock;
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
wrapper = mountExtended(NoteableDiscussion, {
store,
- propsData: { discussion: discussionMock },
+ propsData: { discussion },
});
};
@@ -160,6 +160,23 @@ describe('noteable_discussion component', () => {
false,
);
});
+
+ it('should add `internal-note` class when the discussion is internal', async () => {
+ const softCopyInternalNotes = [...discussionMock.notes];
+ const mockInternalNotes = softCopyInternalNotes.splice(0, 2);
+ mockInternalNotes[0].internal = true;
+
+ const mockDiscussion = {
+ ...discussionMock,
+ notes: [...mockInternalNotes],
+ };
+ wrapper.setProps({ discussion: mockDiscussion });
+ await nextTick();
+
+ const replyWrapper = wrapper.find('[data-testid="reply-wrapper"]');
+ expect(replyWrapper.exists()).toBe(true);
+ expect(replyWrapper.classes('internal-note')).toBe(true);
+ });
});
describe('for resolved thread', () => {
diff --git a/spec/frontend/sidebar/components/status/status_dropdown_spec.js b/spec/frontend/sidebar/components/status/status_dropdown_spec.js
index 229b51ea568..923b171e763 100644
--- a/spec/frontend/sidebar/components/status/status_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/status/status_dropdown_spec.js
@@ -1,17 +1,23 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import StatusDropdown from '~/sidebar/components/status/status_dropdown.vue';
import { statusDropdownOptions } from '~/sidebar/constants';
describe('SubscriptionsDropdown component', () => {
let wrapper;
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
const findHiddenInput = () => wrapper.find('input');
function createComponent() {
- wrapper = shallowMount(StatusDropdown);
+ wrapper = shallowMount(StatusDropdown, {
+ stubs: {
+ GlCollapsibleListbox,
+ GlListboxItem,
+ },
+ });
}
describe('with no value selected', () => {
@@ -20,52 +26,55 @@ describe('SubscriptionsDropdown component', () => {
});
it('renders default text', () => {
- expect(findDropdown().props('text')).toBe('Select status');
+ expect(findDropdown().props('toggleText')).toBe('Select status');
});
- it('renders dropdown items with `is-checked` prop set to `false`', () => {
+ it('renders dropdown items with `isSelected` prop set to `false`', () => {
const dropdownItems = findAllDropdownItems();
- expect(dropdownItems.at(0).props('isChecked')).toBe(false);
- expect(dropdownItems.at(1).props('isChecked')).toBe(false);
+ expect(dropdownItems.at(0).props('isSelected')).toBe(false);
+ expect(dropdownItems.at(1).props('isSelected')).toBe(false);
});
});
describe('when selecting a value', () => {
- const selectItemAtIndex = 0;
+ const optionToSelect = statusDropdownOptions[0];
- beforeEach(async () => {
+ beforeEach(() => {
createComponent();
- await findAllDropdownItems().at(selectItemAtIndex).vm.$emit('click');
+ findDropdown().vm.$emit('select', optionToSelect.value);
});
it('updates value of the hidden input', () => {
- expect(findHiddenInput().attributes('value')).toBe(
- statusDropdownOptions[selectItemAtIndex].value,
- );
+ expect(findHiddenInput().attributes('value')).toBe(optionToSelect.value);
});
it('updates the dropdown text prop', () => {
- expect(findDropdown().props('text')).toBe(statusDropdownOptions[selectItemAtIndex].text);
+ expect(findDropdown().props('toggleText')).toBe(optionToSelect.text);
});
- it('sets dropdown item `is-checked` prop to `true`', () => {
+ it('sets dropdown item `isSelected` prop to `true`', () => {
const dropdownItems = findAllDropdownItems();
- expect(dropdownItems.at(0).props('isChecked')).toBe(true);
- expect(dropdownItems.at(1).props('isChecked')).toBe(false);
+ expect(dropdownItems.at(0).props('isSelected')).toBe(true);
+ expect(dropdownItems.at(1).props('isSelected')).toBe(false);
});
+ });
- describe('when selecting the value that is already selected', () => {
- it('clears dropdown selection', async () => {
- await findAllDropdownItems().at(selectItemAtIndex).vm.$emit('click');
+ describe('when reset is triggered', () => {
+ beforeEach(() => {
+ createComponent();
+ findDropdown().vm.$emit('select', statusDropdownOptions[0].value);
+ });
- const dropdownItems = findAllDropdownItems();
+ it('clears dropdown selection', async () => {
+ findDropdown().vm.$emit('reset');
+ await nextTick();
+ const dropdownItems = findAllDropdownItems();
- expect(dropdownItems.at(0).props('isChecked')).toBe(false);
- expect(dropdownItems.at(1).props('isChecked')).toBe(false);
- expect(findDropdown().props('text')).toBe('Select status');
- });
+ expect(dropdownItems.at(0).props('isSelected')).toBe(false);
+ expect(dropdownItems.at(1).props('isSelected')).toBe(false);
+ expect(findDropdown().props('toggleText')).toBe('Select status');
});
});
});
diff --git a/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js b/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
index eaf7bc13d20..052e6ec9553 100644
--- a/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import SubscriptionsDropdown from '~/sidebar/components/subscriptions/subscriptions_dropdown.vue';
@@ -7,12 +7,17 @@ import { subscriptionsDropdownOptions } from '~/sidebar/constants';
describe('SubscriptionsDropdown component', () => {
let wrapper;
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
const findHiddenInput = () => wrapper.find('input');
function createComponent() {
- wrapper = shallowMount(SubscriptionsDropdown);
+ wrapper = shallowMount(SubscriptionsDropdown, {
+ stubs: {
+ GlCollapsibleListbox,
+ GlListboxItem,
+ },
+ });
}
describe('with no value selected', () => {
@@ -25,48 +30,59 @@ describe('SubscriptionsDropdown component', () => {
});
it('renders default text', () => {
- expect(findDropdown().props('text')).toBe(SubscriptionsDropdown.i18n.defaultDropdownText);
+ expect(findDropdown().props('toggleText')).toBe(
+ SubscriptionsDropdown.i18n.defaultDropdownText,
+ );
});
- it('renders dropdown items with `is-checked` prop set to `false`', () => {
+ it('renders dropdown items with `isSelected` prop set to `false`', () => {
const dropdownItems = findAllDropdownItems();
- expect(dropdownItems.at(0).props('isChecked')).toBe(false);
- expect(dropdownItems.at(1).props('isChecked')).toBe(false);
+ expect(dropdownItems.at(0).props('isSelected')).toBe(false);
+ expect(dropdownItems.at(1).props('isSelected')).toBe(false);
});
});
describe('when selecting a value', () => {
+ const optionToSelect = subscriptionsDropdownOptions[0];
+
beforeEach(() => {
createComponent();
- findAllDropdownItems().at(0).vm.$emit('click');
+ findDropdown().vm.$emit('select', optionToSelect.value);
});
it('updates value of the hidden input', () => {
- expect(findHiddenInput().attributes('value')).toBe(subscriptionsDropdownOptions[0].value);
+ expect(findHiddenInput().attributes('value')).toBe(optionToSelect.value);
});
it('updates the dropdown text prop', () => {
- expect(findDropdown().props('text')).toBe(subscriptionsDropdownOptions[0].text);
+ expect(findDropdown().props('toggleText')).toBe(optionToSelect.text);
});
- it('sets dropdown item `is-checked` prop to `true`', () => {
+ it('sets dropdown item `isSelected` prop to `true`', () => {
const dropdownItems = findAllDropdownItems();
- expect(dropdownItems.at(0).props('isChecked')).toBe(true);
- expect(dropdownItems.at(1).props('isChecked')).toBe(false);
+ expect(dropdownItems.at(0).props('isSelected')).toBe(true);
+ expect(dropdownItems.at(1).props('isSelected')).toBe(false);
+ });
+ });
+
+ describe('when reset is triggered', () => {
+ beforeEach(() => {
+ createComponent();
+ findDropdown().vm.$emit('select', subscriptionsDropdownOptions[0].value);
});
- describe('when selecting the value that is already selected', () => {
- it('clears dropdown selection', async () => {
- findAllDropdownItems().at(0).vm.$emit('click');
- await nextTick();
- const dropdownItems = findAllDropdownItems();
+ it('clears dropdown selection', async () => {
+ findDropdown().vm.$emit('reset');
+ await nextTick();
+ const dropdownItems = findAllDropdownItems();
- expect(dropdownItems.at(0).props('isChecked')).toBe(false);
- expect(dropdownItems.at(1).props('isChecked')).toBe(false);
- expect(findDropdown().props('text')).toBe(SubscriptionsDropdown.i18n.defaultDropdownText);
- });
+ expect(dropdownItems.at(0).props('isSelected')).toBe(false);
+ expect(dropdownItems.at(1).props('isSelected')).toBe(false);
+ expect(findDropdown().props('toggleText')).toBe(
+ SubscriptionsDropdown.i18n.defaultDropdownText,
+ );
});
});
});
diff --git a/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js b/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
index 909f4249e28..771d1f07fea 100644
--- a/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
+++ b/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
@@ -42,22 +42,19 @@ describe('Super Sidebar Collapsed State Manager', () => {
describe('toggleSuperSidebarCollapsed', () => {
it.each`
- collapsed | saveCookie | windowWidth | hasClass | superSidebarPeek | isPeekable
- ${true} | ${true} | ${xl} | ${true} | ${false} | ${false}
- ${true} | ${true} | ${xl} | ${true} | ${true} | ${true}
- ${true} | ${false} | ${xl} | ${true} | ${false} | ${false}
- ${true} | ${true} | ${sm} | ${true} | ${false} | ${false}
- ${true} | ${false} | ${sm} | ${true} | ${false} | ${false}
- ${false} | ${true} | ${xl} | ${false} | ${false} | ${false}
- ${false} | ${true} | ${xl} | ${false} | ${true} | ${false}
- ${false} | ${false} | ${xl} | ${false} | ${false} | ${false}
- ${false} | ${true} | ${sm} | ${false} | ${false} | ${false}
- ${false} | ${false} | ${sm} | ${false} | ${false} | ${false}
+ collapsed | saveCookie | windowWidth | hasClass | isPeekable
+ ${true} | ${true} | ${xl} | ${true} | ${true}
+ ${true} | ${false} | ${xl} | ${true} | ${true}
+ ${true} | ${true} | ${sm} | ${true} | ${true}
+ ${true} | ${false} | ${sm} | ${true} | ${true}
+ ${false} | ${true} | ${xl} | ${false} | ${false}
+ ${false} | ${false} | ${xl} | ${false} | ${false}
+ ${false} | ${true} | ${sm} | ${false} | ${false}
+ ${false} | ${false} | ${sm} | ${false} | ${false}
`(
'when collapsed is $collapsed, saveCookie is $saveCookie, and windowWidth is $windowWidth then page class contains `page-with-super-sidebar-collapsed` is $hasClass',
- ({ collapsed, saveCookie, windowWidth, hasClass, superSidebarPeek, isPeekable }) => {
+ ({ collapsed, saveCookie, windowWidth, hasClass, isPeekable }) => {
jest.spyOn(bp, 'windowWidth').mockReturnValue(windowWidth);
- gon.features = { superSidebarPeek };
toggleSuperSidebarCollapsed(collapsed, saveCookie);
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
index b85a8a93d42..213dee0d19d 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -46,6 +46,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
expect(batched_migration.status_name).to be :finished
end
+
+ it 'updates the finished_at' do
+ freeze_time do
+ expect { batched_migration.finish! }.to change(batched_migration, :finished_at).from(nil).to(Time.current)
+ end
+ end
end
end
diff --git a/spec/models/concerns/packages/downloadable_spec.rb b/spec/models/concerns/packages/downloadable_spec.rb
new file mode 100644
index 00000000000..79e0d684b7c
--- /dev/null
+++ b/spec/models/concerns/packages/downloadable_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Downloadable, feature_category: :package_registry do
+ context 'with a package' do
+ describe '#touch_last_downloaded_at' do
+ let_it_be(:package) { create(:package) }
+
+ subject { package.touch_last_downloaded_at }
+
+ it 'updates the downloaded_at' do
+ expect(::Gitlab::Database::LoadBalancing::Session).to receive(:without_sticky_writes).and_call_original
+ expect { subject }
+ .to change { package.last_downloaded_at }.from(nil).to(instance_of(ActiveSupport::TimeWithZone))
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/npm/metadata_cache_spec.rb b/spec/models/packages/npm/metadata_cache_spec.rb
index 5e7a710baf8..94b41ab6a5e 100644
--- a/spec/models/packages/npm/metadata_cache_spec.rb
+++ b/spec/models/packages/npm/metadata_cache_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Packages::Npm::MetadataCache, type: :model, feature_category: :pa
let_it_be(:package_name) { '@root/test' }
it { is_expected.to be_a FileStoreMounter }
+ it { is_expected.to be_a Packages::Downloadable }
describe 'relationships' do
it { is_expected.to belong_to(:project).inverse_of(:npm_metadata_caches) }
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 5dd7a0982be..90a5d815427 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe Packages::Package, type: :model, feature_category: :package_regis
it_behaves_like 'having unique enum values'
+ it { is_expected.to be_a Packages::Downloadable }
+
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:creator) }
@@ -1421,18 +1423,6 @@ RSpec.describe Packages::Package, type: :model, feature_category: :package_regis
end
end
- describe '#touch_last_downloaded_at' do
- let_it_be(:package) { create(:package) }
-
- subject { package.touch_last_downloaded_at }
-
- it 'updates the downloaded_at' do
- expect(::Gitlab::Database::LoadBalancing::Session).to receive(:without_sticky_writes).and_call_original
- expect { subject }
- .to change(package, :last_downloaded_at).from(nil).to(instance_of(ActiveSupport::TimeWithZone))
- end
- end
-
describe "#publish_creation_event" do
let_it_be(:project) { create(:project) }
diff --git a/spec/presenters/ml/candidate_details_presenter_spec.rb b/spec/presenters/ml/candidate_details_presenter_spec.rb
index d83ffbc7129..9d1f6f634e4 100644
--- a/spec/presenters/ml/candidate_details_presenter_spec.rb
+++ b/spec/presenters/ml/candidate_details_presenter_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe ::Ml::CandidateDetailsPresenter, feature_category: :mlops do
- let_it_be(:project) { create(:project, :private) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
- let_it_be(:user) { project.creator }
+ let_it_be(:user) { create(:user, :with_avatar) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
+ let_it_be(:project) { create(:project, :private, creator: user) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
let_it_be(:experiment) { create(:ml_experiments, user: user, project: project) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
let_it_be(:candidate) do
create(:ml_candidates, :with_artifact, experiment: experiment, user: user, project: project) # rubocop:disable RSpec/FactoryBot/AvoidCreate
@@ -74,7 +74,9 @@ RSpec.describe ::Ml::CandidateDetailsPresenter, feature_category: :mlops do
'name' => 'test',
'user' => {
'path' => "/#{pipeline.user.username}",
- 'username' => pipeline.user.username
+ 'name' => pipeline.user.name,
+ 'username' => pipeline.user.username,
+ 'avatar' => user.avatar_url
}
}
@@ -100,6 +102,7 @@ RSpec.describe ::Ml::CandidateDetailsPresenter, feature_category: :mlops do
it 'generates the correct ci' do
expected_info = {
'path' => "/#{project.full_path}/-/merge_requests/#{mr.iid}",
+ 'iid' => mr.iid,
'title' => mr.title
}
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index cc8be312c71..c1aeb9991cd 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -3480,7 +3480,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
activate
expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden - A blocked user must be unblocked to be activated')
+ expect(json_response['message']).to eq('Error occurred. A blocked user must be unblocked to be activated')
expect(blocked_user.reload.state).to eq('blocked')
end
end
@@ -3494,7 +3494,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
activate
expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden - A blocked user must be unblocked to be activated')
+ expect(json_response['message']).to eq('Error occurred. A blocked user must be unblocked to be activated')
expect(user.reload.state).to eq('ldap_blocked')
end
end
diff --git a/spec/services/users/activate_service_spec.rb b/spec/services/users/activate_service_spec.rb
new file mode 100644
index 00000000000..8b8c0dbdd3e
--- /dev/null
+++ b/spec/services/users/activate_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::ActivateService, feature_category: :user_management do
+ let_it_be(:current_user) { build(:admin) }
+
+ subject(:service) { described_class.new(current_user) }
+
+ describe '#execute' do
+ let!(:user) { create(:user, :deactivated) }
+
+ subject(:operation) { service.execute(user) }
+
+ context 'when successful', :enable_admin_mode do
+ it 'returns success status' do
+ expect(operation[:status]).to eq(:success)
+ end
+
+ it "changes the user's state" do
+ expect { operation }.to change { user.state }.to('active')
+ end
+
+ it 'creates a log entry' do
+ expect(Gitlab::AppLogger).to receive(:info).with(message: "User activated", user: user.username,
+ email: user.email, activated_by: current_user.username, ip_address: current_user.current_sign_in_ip.to_s)
+
+ operation
+ end
+ end
+
+ context 'when the user is already active', :enable_admin_mode do
+ let(:user) { create(:user) }
+
+ it 'returns success result' do
+ aggregate_failures 'success result' do
+ expect(operation[:status]).to eq(:success)
+ expect(operation[:message]).to eq('Successfully activated')
+ end
+ end
+
+ it "does not change the user's state" do
+ expect { operation }.not_to change { user.state }
+ end
+ end
+
+ context 'when user activation fails', :enable_admin_mode do
+ before do
+ allow(user).to receive(:activate).and_return(false)
+ end
+
+ it 'returns an unprocessable entity error' do
+ result = service.execute(user)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:reason]).to eq(:unprocessable_entity)
+ end
+ end
+
+ context 'when user is not an admin' do
+ let(:non_admin_user) { build(:user) }
+ let(:service) { described_class.new(non_admin_user) }
+
+ it 'returns permissions error message' do
+ expect(operation[:status]).to eq(:error)
+ expect(operation[:message]).to eq("You are not authorized to perform this action")
+ expect(operation.reason).to eq :forbidden
+ end
+ end
+ end
+end