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:
Diffstat (limited to 'spec')
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb1
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb1
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb1
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb1
-rw-r--r--spec/frontend/boards/mock_data.js19
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js1
-rw-r--r--spec/frontend/issues/list/mock_data.js9
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js30
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js43
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap2
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js25
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemfile_linker_spec.js13
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js2
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js12
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb10
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb8
-rw-r--r--spec/models/event_spec.rb6
-rw-r--r--spec/requests/api/groups_spec.rb17
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/services/ci/runners/bulk_delete_runners_service_spec.rb7
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb4
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb4
-rw-r--r--spec/support/shared_examples/features/packages_shared_examples.rb2
33 files changed, 221 insertions, 57 deletions
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 05eb656461e..40b0bfd9aa4 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -9,6 +9,10 @@ RSpec.describe 'Dropdown assignee', :js do
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue, project: project) }
+ before do
+ stub_feature_flags(or_issuable_queries: false)
+ end
+
describe 'behavior' do
before do
project.add_maintainer(user)
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 36a8f1f3902..a67d114c6d1 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe 'Dropdown author', :js do
let_it_be(:issue) { create(:issue, project: project) }
before do
+ stub_feature_flags(or_issuable_queries: false)
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index dcbab308efa..cbe917931aa 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe 'Dropdown hint', :js do
let_it_be(:issue) { create(:issue, project: project) }
before do
+ stub_feature_flags(or_issuable_queries: false)
project.add_maintainer(user)
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 8d96bbc38cb..e48df1b1c53 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe 'Filter issues', :js do
end
before do
+ stub_feature_flags(or_issuable_queries: false)
project.add_maintainer(user)
create(:issue, project: project, author: user2, title: "Bug report 1")
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index c44181a60e4..854b88c3f81 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe 'Visual tokens', :js do
let_it_be(:issue) { create(:issue, project: project) }
before do
+ stub_feature_flags(or_issuable_queries: false)
project.add_member(user, :maintainer)
project.add_member(user_rock, :maintainer)
sign_in(user)
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index c99ae118db7..a18b79d00a0 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -2,6 +2,8 @@ import { GlFilteredSearchToken } from '@gitlab/ui';
import { keyBy } from 'lodash';
import { ListType } from '~/boards/constants';
import {
+ OPERATOR_IS_AND_IS_NOT,
+ OPERATOR_IS_ONLY,
TOKEN_TITLE_ASSIGNEE,
TOKEN_TITLE_AUTHOR,
TOKEN_TITLE_LABEL,
@@ -739,7 +741,7 @@ export const mockConfidentialToken = {
title: 'Confidential',
unique: true,
token: GlFilteredSearchToken,
- operators: [{ value: '=', description: 'is' }],
+ operators: OPERATOR_IS_ONLY,
options: [
{ icon: 'eye-slash', value: 'yes', title: 'Yes' },
{ icon: 'eye', value: 'no', title: 'No' },
@@ -751,10 +753,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
icon: 'user',
title: TOKEN_TITLE_ASSIGNEE,
type: 'assignee',
- operators: [
- { value: '=', description: 'is' },
- { value: '!=', description: 'is not' },
- ],
+ operators: OPERATOR_IS_AND_IS_NOT,
token: AuthorToken,
unique: true,
fetchAuthors,
@@ -764,10 +763,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
icon: 'pencil',
title: TOKEN_TITLE_AUTHOR,
type: 'author',
- operators: [
- { value: '=', description: 'is' },
- { value: '!=', description: 'is not' },
- ],
+ operators: OPERATOR_IS_AND_IS_NOT,
symbol: '@',
token: AuthorToken,
unique: true,
@@ -778,10 +774,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
icon: 'labels',
title: TOKEN_TITLE_LABEL,
type: 'label',
- operators: [
- { value: '=', description: 'is' },
- { value: '!=', description: 'is not' },
- ],
+ operators: OPERATOR_IS_AND_IS_NOT,
token: LabelToken,
unique: false,
symbol: '~',
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index fa0195ed765..a5488941791 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -89,7 +89,6 @@ describe('CE IssuesListApp component', () => {
hasIssuableHealthStatusFeature: true,
hasIssueWeightsFeature: true,
hasIterationsFeature: true,
- hasMultipleIssueAssigneesFeature: true,
hasScopedLabelsFeature: true,
initialEmail: 'email@example.com',
initialSort: CREATED_DESC,
diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js
index 42e9d348b16..e80191a95c8 100644
--- a/spec/frontend/issues/list/mock_data.js
+++ b/spec/frontend/issues/list/mock_data.js
@@ -1,6 +1,7 @@
import {
OPERATOR_IS,
OPERATOR_IS_NOT,
+ OPERATOR_OR,
} from '~/vue_shared/components/filtered_search_bar/constants';
export const getIssuesQueryResponse = {
@@ -122,6 +123,8 @@ export const locationSearch = [
'assignee_username[]=5',
'not[assignee_username][]=patty',
'not[assignee_username][]=selma',
+ 'or[assignee_username][]=carl',
+ 'or[assignee_username][]=lenny',
'milestone_title=season+3',
'milestone_title=season+4',
'not[milestone_title]=season+20',
@@ -173,6 +176,8 @@ export const filteredTokens = [
{ type: 'assignee_username', value: { data: '5', operator: OPERATOR_IS } },
{ type: 'assignee_username', value: { data: 'patty', operator: OPERATOR_IS_NOT } },
{ type: 'assignee_username', value: { data: 'selma', operator: OPERATOR_IS_NOT } },
+ { type: 'assignee_username', value: { data: 'carl', operator: OPERATOR_OR } },
+ { type: 'assignee_username', value: { data: 'lenny', operator: OPERATOR_OR } },
{ type: 'milestone', value: { data: 'season 3', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 4', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 20', operator: OPERATOR_IS_NOT } },
@@ -244,6 +249,9 @@ export const apiParams = {
epicId: '34',
weight: '3',
},
+ or: {
+ assigneeUsernames: ['carl', 'lenny'],
+ },
};
export const apiParamsWithSpecialValues = {
@@ -263,6 +271,7 @@ export const urlParams = {
'not[author_username]': 'marge',
'assignee_username[]': ['bart', 'lisa', '5'],
'not[assignee_username][]': ['patty', 'selma'],
+ 'or[assignee_username][]': ['carl', 'lenny'],
milestone_title: ['season 3', 'season 4'],
'not[milestone_title]': ['season 20', 'season 30'],
'label_name[]': ['cartoon', 'tv'],
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
index 2e7449974e5..0ce3f75f576 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
@@ -52,6 +52,9 @@ describe('Timeline events form', () => {
findMinuteInput().setValue(45);
};
const findTextarea = () => wrapper.findByTestId('input-note');
+ const findCountNumeric = (count) => wrapper.findByText(count);
+ const findCountVerbose = (count) => wrapper.findByText(`${count} characters remaining`);
+ const findCountHint = () => wrapper.findByText(timelineFormI18n.hint);
const submitForm = async () => {
findSubmitButton().vm.$emit('click');
@@ -135,4 +138,31 @@ describe('Timeline events form', () => {
expect(findSubmitAndAddButton().props('disabled')).toBe(false);
});
});
+
+ describe('form character limit', () => {
+ beforeEach(() => {
+ mountComponent({ mountMethod: mountExtended });
+ });
+
+ it('sets a character limit hint', () => {
+ expect(findCountHint().exists()).toBe(true);
+ });
+
+ it('sets a character limit when text is entered', async () => {
+ await findTextarea().setValue('hello');
+
+ expect(findCountNumeric('275').text()).toBe('275');
+ expect(findCountVerbose('275').text()).toBe('275 characters remaining');
+ });
+
+ it('prevents form submission when text is beyond maximum length', async () => {
+ // 281 characters long
+ const longText =
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in volupte';
+ await findTextarea().setValue(longText);
+
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ expect(findSubmitAndAddButton().props('disabled')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
index e086ee84073..c5b6b7da65b 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
@@ -1,9 +1,10 @@
-import { GlAlert, GlKeysetPagination, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlSprintf } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
+import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import {
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
@@ -38,9 +39,9 @@ describe('packages_list', () => {
const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' };
const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader);
- const findPackageListPagination = () => wrapper.findComponent(GlKeysetPagination);
const findPackageListDeleteModal = () => wrapper.findComponent(DeletePackageModal);
const findEmptySlot = () => wrapper.findComponent(EmptySlotStub);
+ const findRegistryList = () => wrapper.findComponent(RegistryList);
const findPackagesListRow = () => wrapper.findComponent(PackagesListRow);
const findErrorPackageAlert = () => wrapper.findComponent(GlAlert);
@@ -53,6 +54,7 @@ describe('packages_list', () => {
stubs: {
DeletePackageModal,
GlSprintf,
+ RegistryList,
},
slots: {
'empty-state': EmptySlotStub,
@@ -73,12 +75,12 @@ describe('packages_list', () => {
expect(findPackagesListLoader().exists()).toBe(true);
});
- it('does not show the rows', () => {
- expect(findPackagesListRow().exists()).toBe(false);
+ it('does not show the registry list', () => {
+ expect(findRegistryList().exists()).toBe(false);
});
- it('does not show the pagination', () => {
- expect(findPackageListPagination().exists()).toBe(false);
+ it('does not show the rows', () => {
+ expect(findPackagesListRow().exists()).toBe(false);
});
});
@@ -91,18 +93,25 @@ describe('packages_list', () => {
expect(findPackagesListLoader().exists()).toBe(false);
});
+ it('shows the registry list', () => {
+ expect(findRegistryList().exists()).toBe(true);
+ });
+
+ it('shows the registry list with the right props', () => {
+ expect(findRegistryList().props()).toMatchObject({
+ items: defaultProps.list,
+ pagination: defaultProps.pageInfo,
+ isLoading: false,
+ hiddenDelete: true,
+ });
+ });
+
it('shows the rows', () => {
expect(findPackagesListRow().exists()).toBe(true);
});
});
describe('layout', () => {
- it('contains a pagination component', () => {
- mountComponent({ pageInfo: { hasPreviousPage: true } });
-
- expect(findPackageListPagination().exists()).toBe(true);
- });
-
it("doesn't contain a visible modal component", () => {
mountComponent();
@@ -122,7 +131,7 @@ describe('packages_list', () => {
await findPackagesListRow().vm.$emit('packageToDelete', firstPackage);
});
- it('passes itemToBeDeleted to the modla', () => {
+ it('passes itemToBeDeleted to the modal', () => {
expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(firstPackage);
});
@@ -182,15 +191,15 @@ describe('packages_list', () => {
});
it('emits prev-page events when the prev event is fired', () => {
- findPackageListPagination().vm.$emit('prev');
+ findRegistryList().vm.$emit('prev-page');
- expect(wrapper.emitted('prev-page')).toEqual([[]]);
+ expect(wrapper.emitted('prev-page')).toHaveLength(1);
});
it('emits next-page events when the next event is fired', () => {
- findPackageListPagination().vm.$emit('next');
+ findRegistryList().vm.$emit('next-page');
- expect(wrapper.emitted('next-page')).toEqual([[]]);
+ expect(wrapper.emitted('next-page')).toHaveLength(1);
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
index 41220aaf306..bf50ae42794 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue renders given data 1`] = `
-"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\">
+"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\" helppopover=\\"[object Object]\\">
<div class=\\"gl-display-flex gl-flex-direction-column\\">
<div>
<p class=\\"gl-mb-0\\">Main text for the row</p>
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js
index b7753a58747..527e800ddcf 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js
@@ -25,6 +25,10 @@ describe('~/vue_merge_request_widget/components/widget/dynamic_content.vue', ()
header: ['This is a header', 'This is a subheader'],
text: 'Main text for the row',
subtext: 'Optional: Smaller sub-text to be displayed below the main text',
+ helpPopover: {
+ options: { title: 'Widget help popover title' },
+ content: { text: 'Widget help popover content' },
+ },
icon: {
name: EXTENSION_ICONS.success,
},
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
index f885db86ea1..606f7696694 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
@@ -1,11 +1,13 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue';
import StatusIcon from '~/vue_merge_request_widget/components/widget/status_icon.vue';
+import HelpPopover from '~/vue_shared/components/help_popover.vue';
describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue', () => {
let wrapper;
const findStatusIcon = () => wrapper.findComponent(StatusIcon);
+ const findHelpPopover = () => wrapper.findComponent(HelpPopover);
const createComponent = ({ propsData, slots } = {}) => {
wrapper = shallowMountExtended(WidgetContentRow, {
@@ -36,14 +38,12 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue',
},
slots: {
header: '<span>this is a header</span>',
- 'header-actions': '<span>this is a header action</span>',
body: '<span>this is a body</span>',
},
});
expect(wrapper.findByText('this is a body').exists()).toBe(true);
expect(wrapper.findByText('this is a header').exists()).toBe(true);
- expect(wrapper.findByText('this is a header action').exists()).toBe(true);
});
});
@@ -63,5 +63,26 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue',
createComponent({ propsData: { header: '<b role="header">this is a header</b>' } });
expect(wrapper.findByText('<b role="header">this is a header</b>').exists()).toBe(true);
});
+
+ it('renders a help popover', () => {
+ createComponent({
+ propsData: {
+ helpPopover: {
+ options: { title: 'Help popover title' },
+ content: { text: 'Help popover content', learnMorePath: '/path/to/docs' },
+ },
+ },
+ });
+
+ expect(findHelpPopover().props('options')).toEqual({ title: 'Help popover title' });
+ expect(wrapper.findByText('Help popover content').exists()).toBe(true);
+ expect(wrapper.findByText('Learn more').attributes('href')).toBe('/path/to/docs');
+ expect(wrapper.findByText('Learn more').attributes('target')).toBe('_blank');
+ });
+
+ it('does not render help popover when it is not provided', () => {
+ createComponent({});
+ expect(findHelpPopover().exists()).toBe(false);
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
index 814a1e68142..f68cb21f2cf 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
@@ -1,6 +1,7 @@
import { nextTick } from 'vue';
import * as Sentry from '@sentry/browser';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import HelpPopover from '~/vue_shared/components/help_popover.vue';
import waitForPromises from 'helpers/wait_for_promises';
import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
import ActionButtons from '~/vue_merge_request_widget/components/action_buttons.vue';
@@ -22,6 +23,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section');
const findActionButtons = () => wrapper.findComponent(ActionButtons);
const findToggleButton = () => wrapper.findByTestId('toggle-button');
+ const findHelpPopover = () => wrapper.findComponent(HelpPopover);
const createComponent = ({ propsData, slots } = {}) => {
wrapper = shallowMountExtended(Widget, {
@@ -201,6 +203,30 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
});
});
+ describe('help popover', () => {
+ it('renders a help popover', () => {
+ createComponent({
+ propsData: {
+ fetchCollapsedData: jest.fn(),
+ helpPopover: {
+ options: { title: 'My help popover title' },
+ content: { text: 'Help popover content', learnMorePath: '/path/to/docs' },
+ },
+ },
+ });
+
+ expect(findHelpPopover().props('options')).toEqual({ title: 'My help popover title' });
+ expect(wrapper.findByText('Help popover content').exists()).toBe(true);
+ expect(wrapper.findByText('Learn more').attributes('href')).toBe('/path/to/docs');
+ expect(wrapper.findByText('Learn more').attributes('target')).toBe('_blank');
+ });
+
+ it('does not render help popover when it is not provided', () => {
+ createComponent({ propsData: { fetchCollapsedData: jest.fn() } });
+ expect(findHelpPopover().exists()).toBe(false);
+ });
+ });
+
describe('handle collapse toggle', () => {
it('displays the toggle button correctly', () => {
createComponent({
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js
index 032f2985d35..cfd493663b7 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js
@@ -1,17 +1,20 @@
import packageJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/package_json_linker';
import godepsJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker';
import gemspecLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker';
+import gemfileLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker';
import linkDependencies from '~/vue_shared/components/source_viewer/plugins/link_dependencies';
import {
PACKAGE_JSON_FILE_TYPE,
PACKAGE_JSON_CONTENT,
GEMSPEC_FILE_TYPE,
GODEPS_JSON_FILE_TYPE,
+ GEMFILE_FILE_TYPE,
} from './mock_data';
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/package_json_linker');
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker');
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker');
+jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker');
describe('Highlight.js plugin for linking dependencies', () => {
const hljsResultMock = { value: 'test' };
@@ -30,4 +33,9 @@ describe('Highlight.js plugin for linking dependencies', () => {
linkDependencies(hljsResultMock, GODEPS_JSON_FILE_TYPE);
expect(godepsJsonLinker).toHaveBeenCalled();
});
+
+ it('calls gemfileLinker for gemfile file types', () => {
+ linkDependencies(hljsResultMock, GEMFILE_FILE_TYPE);
+ expect(gemfileLinker).toHaveBeenCalled();
+ });
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js
index 3146600e491..4f390cebd37 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js
@@ -4,3 +4,5 @@ export const PACKAGE_JSON_CONTENT = '{ "dependencies": { "@babel/core": "^7.18.5
export const GEMSPEC_FILE_TYPE = 'gemspec';
export const GODEPS_JSON_FILE_TYPE = 'godeps_json';
+
+export const GEMFILE_FILE_TYPE = 'gemfile';
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js
index e4ce07ec668..e1dbdf8a87d 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js
@@ -7,7 +7,7 @@ describe('createLink', () => {
it('generates a link with the correct attributes', () => {
const href = 'http://test.com';
const innerText = 'testing';
- const result = `<a href="${href}" rel="nofollow noreferrer noopener">${innerText}</a>`;
+ const result = `<a href="${href}" target="_blank" rel="nofollow noreferrer noopener">${innerText}</a>`;
expect(createLink(href, innerText)).toBe(result);
});
@@ -18,7 +18,7 @@ describe('createLink', () => {
const escapedHref = '&lt;script&gt;XSS&lt;/script&gt;';
const href = `http://test.com/${unescapedXSS}`;
const innerText = `testing${unescapedXSS}`;
- const result = `<a href="http://test.com/${escapedHref}" rel="nofollow noreferrer noopener">testing${escapedPackageName}</a>`;
+ const result = `<a href="http://test.com/${escapedHref}" target="_blank" rel="nofollow noreferrer noopener">testing${escapedPackageName}</a>`;
expect(createLink(href, innerText)).toBe(result);
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemfile_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemfile_linker_spec.js
new file mode 100644
index 00000000000..4e188c9af7e
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemfile_linker_spec.js
@@ -0,0 +1,13 @@
+import gemfileLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker';
+
+describe('Highlight.js plugin for linking gemfile dependencies', () => {
+ it('mutates the input value by wrapping dependency names in anchors', () => {
+ const inputValue = 'gem </span><span class="hljs-string">&#39;paranoia&#39;';
+ const outputValue =
+ 'gem </span><span class="hljs-string">&#39;<a href="https://rubygems.org/gems/paranoia" target="_blank" rel="nofollow noreferrer noopener">paranoia</a>&#39;';
+ const hljsResultMock = { value: inputValue };
+
+ const output = gemfileLinker(hljsResultMock);
+ expect(output).toBe(outputValue);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js
index 3f74bfa117f..4b104b0bf43 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js
@@ -5,7 +5,7 @@ describe('Highlight.js plugin for linking gemspec dependencies', () => {
const inputValue =
's.add_dependency(<span class="hljs-string">&#x27;rugged&#x27;</span>, <span class="hljs-string">&#x27;~&gt; 0.24.0&#x27;</span>)';
const outputValue =
- 's.add_dependency(<span class="hljs-string linked">&#x27;<a href="https://rubygems.org/gems/rugged" rel="nofollow noreferrer noopener">rugged</a>&#x27;</span>, <span class="hljs-string">&#x27;~&gt; 0.24.0&#x27;</span>)';
+ 's.add_dependency(<span class="hljs-string linked">&#x27;<a href="https://rubygems.org/gems/rugged" target="_blank" rel="nofollow noreferrer noopener">rugged</a>&#x27;</span>, <span class="hljs-string">&#x27;~&gt; 0.24.0&#x27;</span>)';
const hljsResultMock = { value: inputValue };
const output = gemspecLinker(hljsResultMock);
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js
index 1d8b11cf707..ea7e3936846 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js
@@ -3,7 +3,7 @@ import godepsJsonLinker from '~/vue_shared/components/source_viewer/plugins/util
const getInputValue = (dependencyString) =>
`<span class="hljs-attr">&quot;ImportPath&quot;</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-string">&quot;${dependencyString}&quot;</span>`;
const getOutputValue = (dependencyString, expectedHref) =>
- `<span class="hljs-attr">&quot;ImportPath&quot;</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-attr">&quot;<a href="${expectedHref}" rel="nofollow noreferrer noopener">${dependencyString}</a>&quot;</span>`;
+ `<span class="hljs-attr">&quot;ImportPath&quot;</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-attr">&quot;<a href="${expectedHref}" target="_blank" rel="nofollow noreferrer noopener">${dependencyString}</a>&quot;</span>`;
describe('Highlight.js plugin for linking Godeps.json dependencies', () => {
it.each`
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js
index e83c129818c..170a44f8ee2 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js
@@ -6,7 +6,7 @@ describe('Highlight.js plugin for linking package.json dependencies', () => {
const inputValue =
'<span class="hljs-attr">&quot;@babel/core&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;^7.18.5&quot;</span>';
const outputValue =
- '<span class="hljs-attr">&quot;<a href="https://npmjs.com/package/@babel/core" rel="nofollow noreferrer noopener">@babel/core</a>&quot;</span>: <span class="hljs-attr">&quot;<a href="https://npmjs.com/package/@babel/core" rel="nofollow noreferrer noopener">^7.18.5</a>&quot;</span>';
+ '<span class="hljs-attr">&quot;<a href="https://npmjs.com/package/@babel/core" target="_blank" rel="nofollow noreferrer noopener">@babel/core</a>&quot;</span>: <span class="hljs-attr">&quot;<a href="https://npmjs.com/package/@babel/core" target="_blank" rel="nofollow noreferrer noopener">^7.18.5</a>&quot;</span>';
const hljsResultMock = { value: inputValue };
const output = packageJsonLinker(hljsResultMock, PACKAGE_JSON_CONTENT);
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
index 0c53f599d55..371844e66f4 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
@@ -322,6 +322,18 @@ describe('IssuableListRoot', () => {
});
});
+ describe('showFilteredSearchFriendlyText prop', () => {
+ describe.each([true, false])('when %s', (showFilteredSearchFriendlyText) => {
+ it('passes its value to FilteredSearchBar', () => {
+ wrapper = createComponent({ props: { showFilteredSearchFriendlyText } });
+
+ expect(findFilteredSearchBar().props('showFriendlyText')).toBe(
+ showFilteredSearchFriendlyText,
+ );
+ });
+ });
+ });
+
describe('alert', () => {
const error = 'oopsie!';
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index d31ccccd6c3..9e77137795a 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe Banzai::ReferenceParser::BaseParser do
context 'when the link does not have a data-project attribute' do
it 'returns the nodes' do
- expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ expect(subject.nodes_visible_to_user(user, [link])).to match_array([link])
end
end
end
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 8e962c0252e..3569a1019f0 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -31,6 +31,12 @@ RSpec.describe Banzai::ReferenceParser::CommitParser do
expect(subject.nodes_visible_to_user(user, [link])).to be_empty
end
end
+
+ context 'when the link does not have a data-project attribute' do
+ it 'returns the nodes' do
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+ end
end
describe '#referenced_by' do
@@ -141,7 +147,7 @@ RSpec.describe Banzai::ReferenceParser::CommitParser do
end
end
- context 'when checking commits on another projects' do
+ context 'when checking commits on another projects', :request_store do
let!(:control_links) do
[commit_link]
end
@@ -153,7 +159,7 @@ RSpec.describe Banzai::ReferenceParser::CommitParser do
def commit_link
project = create(:project, :repository, :public)
- Nokogiri::HTML.fragment(%Q{<a data-commit="#{project.commit.id}" data-project="#{project.id}"></a>}).children[0]
+ Nokogiri::HTML.fragment(%(<a data-commit="#{project.commit.id}" data-project="#{project.id}"></a>)).children[0]
end
it_behaves_like 'no project N+1 queries'
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index 1b8214ebcbb..172347fc421 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -31,6 +31,12 @@ RSpec.describe Banzai::ReferenceParser::CommitRangeParser do
expect(subject.nodes_visible_to_user(user, [link])).to be_empty
end
end
+
+ context 'when the link does not have a data-project attribute' do
+ it 'returns the nodes' do
+ expect(subject.nodes_visible_to_user(user, [link])).to match_array([link])
+ end
+ end
end
describe '#referenced_by' do
@@ -149,7 +155,7 @@ RSpec.describe Banzai::ReferenceParser::CommitRangeParser do
end
end
- context 'when checking commits ranges on another project' do
+ context 'when checking commits ranges on another projects', :request_store do
let!(:control_links) do
[commit_range_link]
end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index bc72d28ae77..9579c4c2d27 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Event do
describe 'after_create :set_last_repository_updated_at' do
context 'with a push event' do
it 'updates the project last_repository_updated_at and updated_at' do
- project.touch(:last_repository_updated_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations
+ project.touch(:last_repository_updated_at, time: 1.year.ago)
event = create_push_event(project, project.first_owner)
@@ -864,7 +864,7 @@ RSpec.describe Event do
end
it 'updates the project' do
- project.touch(:last_activity_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations
+ project.touch(:last_activity_at, time: 1.year.ago)
event = create_push_event(project, project.first_owner)
@@ -880,7 +880,7 @@ RSpec.describe Event do
"project:#{project.id}")
end
- project.touch(:last_activity_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations
+ project.touch(:last_activity_at, time: 1.year.ago)
create_push_event(project, project.first_owner)
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 4a989b4d839..ce6140d8da8 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -1227,10 +1227,7 @@ RSpec.describe API::Groups do
group1.reload
end
- it "only looks up root ancestor once and returns projects including those in subgroups" do
- expect(Namespace).to receive(:find_by).with(id: group1.id.to_s).once.and_call_original # For the group sent in the API call
- expect(Namespace).to receive(:joins).with(start_with('INNER JOIN (SELECT id, traversal_ids[1]')).once.and_call_original # All-in-one root_ancestor query
-
+ it "returns projects including those in subgroups" do
get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true }
expect(response).to have_gitlab_http_status(:ok)
@@ -1238,6 +1235,18 @@ RSpec.describe API::Groups do
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(6)
end
+
+ it 'avoids N+1 queries', :use_sql_query_cache do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true }
+ end
+
+ create_list(:project, 2, :public, namespace: group1)
+
+ expect do
+ get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true }
+ end.not_to exceed_all_query_limit(control.count)
+ end
end
context 'when include_ancestor_groups is true' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 29713d11f87..3831e8e1dfe 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1065,7 +1065,7 @@ RSpec.describe API::Projects do
let_it_be(:admin) { create(:admin) }
- it 'avoids N+1 queries' do
+ it 'avoids N+1 queries', :use_sql_query_cache do
get api('/projects', admin)
base_project = create(:project, :public, namespace: admin.namespace)
@@ -1073,7 +1073,7 @@ RSpec.describe API::Projects do
fork_project1 = fork_project(base_project, admin, namespace: create(:user).namespace)
fork_project2 = fork_project(fork_project1, admin, namespace: create(:user).namespace)
- control = ActiveRecord::QueryRecorder.new do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
get api('/projects', admin)
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 4d9bdc4bb17..cd6b8c77291 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -163,10 +163,8 @@ RSpec.describe PipelineSerializer do
context 'with different refs' do
before do
- # rubocop:disable Rails/SkipsModelValidations
Ci::Pipeline.update_all(%(ref = 'feature-' || id))
Ci::Build.update_all(%(ref = 'feature-' || stage_id))
- # rubocop:enable Rails/SkipsModelValidations
end
it 'verifies number of queries', :request_store do
diff --git a/spec/services/ci/runners/bulk_delete_runners_service_spec.rb b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb
index 5bab54c75b6..fa8af1100df 100644
--- a/spec/services/ci/runners/bulk_delete_runners_service_spec.rb
+++ b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb
@@ -121,6 +121,13 @@ RSpec.describe ::Ci::Runners::BulkDeleteRunnersService, '#execute' do
service = described_class.new(runners: runners_arg, current_user: user)
+ # Base cost per runner is:
+ # - 1 `SELECT * FROM "taggings"` query
+ # - 1 `SAVEPOINT` query
+ # - 1 `DELETE FROM "ci_runners"` query
+ # - 1 `RELEASE SAVEPOINT` query
+ # Project runners have an additional query:
+ # - 1 `DELETE FROM "ci_runner_projects"` query, given the call to `destroy_all`
instance_runner_cost = 4
group_runner_cost = 4
project_runner_cost = 5
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 93122ca3d0c..677cea7b804 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -254,6 +254,10 @@ module FilteredSearchHelpers
expect(page).to have_css '.gl-filtered-search-token', text: "Assignee = #{value}"
end
+ def expect_unioned_assignee_token(value)
+ expect(page).to have_css '.gl-filtered-search-token', text: "Assignee is one of #{value}"
+ end
+
def expect_author_token(value)
expect(page).to have_css '.gl-filtered-search-token', text: "Author = #{value}"
end
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index 9aee9025b12..370dedabd9b 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -12,14 +12,14 @@ module ReferenceParserHelpers
end
RSpec.shared_examples 'no project N+1 queries' do
- it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
+ it 'avoids N+1 queries in #nodes_visible_to_user', :use_sql_query_cache do
context = Banzai::RenderContext.new(project, user)
request = lambda do |links|
described_class.new(context).nodes_visible_to_user(user, links)
end
- control = ActiveRecord::QueryRecorder.new { request.call(control_links) }
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { request.call(control_links) }
create(:group_member, group: project.group) if project.group
create(:project_member, project: project)
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index 7aad5e2de80..f09cf0613a1 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -14,7 +14,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false|
end
def package_table_row(index)
- page.all("#{packages_table_selector} > [data-testid=\"package-row\"]")[index].text
+ page.all("#{packages_table_selector} [data-testid=\"package-row\"]")[index].text
end
end