diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-07 15:09:00 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-07 15:09:00 +0300 |
commit | ba27dbddc7dbc42f2cc8d84e815a9ea19f87a81d (patch) | |
tree | e71fba864897fa78be7f0c40ded23d0f719abf84 /spec | |
parent | 708815aefead73a61473c1a611aea169ab16a358 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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 |