summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-05 15:09:12 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-05 15:09:12 +0000
commite2937892231e082f4981c31e25cb8d1cca36ea60 (patch)
treea543551ce5980395b9ee826c78e83d4d9c1ae9d4 /spec
parentfdb953945da752dc52c1957f64a179de39f507e5 (diff)
downloadgitlab-ce-e2937892231e082f4981c31e25cb8d1cca36ea60.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb10
-rw-r--r--spec/features/projects/show/user_uploads_files_spec.rb34
-rw-r--r--spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap40
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_form_spec.js10
-rw-r--r--spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap70
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js125
-rw-r--r--spec/frontend/single_file_diff_spec.js96
-rw-r--r--spec/graphql/mutations/user_callouts/create_spec.rb42
-rw-r--r--spec/graphql/types/user_callout_feature_name_enum_spec.rb11
-rw-r--r--spec/graphql/types/user_callout_type_spec.rb11
-rw-r--r--spec/graphql/types/user_type_spec.rb9
-rw-r--r--spec/helpers/ide_helper_spec.rb47
-rw-r--r--spec/lib/gitlab/usage/docs/renderer_spec.rb8
-rw-r--r--spec/lib/gitlab/usage/docs/value_formatter_spec.rb6
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb38
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb2
-rw-r--r--spec/models/ci/runner_spec.rb53
-rw-r--r--spec/models/merge_request_spec.rb29
-rw-r--r--spec/models/project_spec.rb28
-rw-r--r--spec/models/user_spec.rb39
-rw-r--r--spec/requests/api/graphql/mutations/user_callouts/create_spec.rb29
-rw-r--r--spec/requests/ide_controller_spec.rb174
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb1
-rw-r--r--spec/services/merge_requests/after_create_service_spec.rb4
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb12
-rw-r--r--spec/services/users/dismiss_user_callout_service_spec.rb27
-rw-r--r--spec/support/shared_examples/features/project_upload_files_shared_examples.rb20
28 files changed, 868 insertions, 109 deletions
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 55bdf4c244e..cbd1ae628d1 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -17,7 +17,6 @@ RSpec.describe 'Expand and collapse diffs', :js do
# Ensure that undiffable.md is in .gitattributes
project.repository.copy_gitattributes(branch)
visit project_commit_path(project, project.commit(branch))
- execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
end
def file_container(filename)
@@ -191,10 +190,6 @@ RSpec.describe 'Expand and collapse diffs', :js do
expect(small_diff).to have_selector('.code')
expect(small_diff).not_to have_selector('.nothing-here-block')
end
-
- it 'does not make a new HTTP request' do
- expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md'))
- end
end
end
@@ -264,7 +259,6 @@ RSpec.describe 'Expand and collapse diffs', :js do
find('.note-textarea')
wait_for_requests
- execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
end
it 'reloads the page with all diffs expanded' do
@@ -300,10 +294,6 @@ RSpec.describe 'Expand and collapse diffs', :js do
expect(small_diff).to have_selector('.code')
expect(small_diff).not_to have_selector('.nothing-here-block')
end
-
- it 'does not make a new HTTP request' do
- expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md'))
- end
end
end
end
diff --git a/spec/features/projects/show/user_uploads_files_spec.rb b/spec/features/projects/show/user_uploads_files_spec.rb
index b7c5d324d93..2030c4d998a 100644
--- a/spec/features/projects/show/user_uploads_files_spec.rb
+++ b/spec/features/projects/show/user_uploads_files_spec.rb
@@ -34,33 +34,23 @@ RSpec.describe 'Projects > Show > User uploads files' do
include_examples 'it uploads and commit a new file to a forked project'
end
- context 'with an empty repo' do
- let(:project) { create(:project, :empty_repo, creator: user) }
-
- context 'when in the empty_repo_upload experiment' do
- before do
- stub_experiments(empty_repo_upload: :candidate)
-
- visit(project_path(project))
- end
-
- it 'uploads and commits a new text file', :js do
- click_link('Upload file')
+ context 'when in the empty_repo_upload experiment' do
+ before do
+ stub_experiments(empty_repo_upload: :candidate)
- drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+ visit(project_path(project))
+ end
- page.within('#modal-upload-blob') do
- fill_in(:commit_message, with: 'New commit message')
- end
+ context 'with an empty repo' do
+ let(:project) { create(:project, :empty_repo, creator: user) }
- click_button('Upload file')
+ include_examples 'uploads and commits a new text file via "upload file" button'
+ end
- wait_for_requests
+ context 'with a nonempty repo' do
+ let(:project) { create(:project, :repository, creator: user) }
- expect(page).to have_content('New commit message')
- expect(page).to have_content('Lorem ipsum dolor sit amet')
- expect(page).to have_content('Sed ut perspiciatis unde omnis')
- end
+ include_examples 'uploads and commits a new text file via "upload file" button'
end
end
end
diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap
index ef6a7621b51..1f8429af7dd 100644
--- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap
+++ b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap
@@ -41,14 +41,14 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
role="presentation"
>
<a
- aria-controls="__BVID__22"
+ aria-controls="__BVID__19"
aria-disabled="true"
aria-posinset="2"
aria-selected="false"
aria-setsize="3"
class="nav-link disabled disabled gl-tab-nav-item"
href="#"
- id="__BVID__22___BV_tab_button__"
+ id="__BVID__19___BV_tab_button__"
role="tab"
tabindex="-1"
target="_self"
@@ -112,7 +112,7 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
for="integration-type"
id="integration-type__BV_label_"
>
- Select integration type
+ 1.Select integration type
</label>
<div
class="bv-no-focus-ring"
@@ -150,19 +150,6 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
>
<!---->
- <span>
- Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the
- <a
- class="gl-link gl-display-inline-block"
- href="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
- rel="noopener noreferrer"
- target="_blank"
- >
- GitLab documentation
- </a>
- to learn more about configuring your endpoint.
- </span>
-
<label
class="gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal"
>
@@ -206,10 +193,6 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
<!---->
<!---->
-
- <!---->
-
- <!---->
</div>
<div
@@ -264,12 +247,25 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
>
<div
aria-hidden="true"
- aria-labelledby="__BVID__22___BV_tab_button__"
+ aria-labelledby="__BVID__19___BV_tab_button__"
class="tab-pane disabled"
- id="__BVID__22"
+ id="__BVID__19"
role="tabpanel"
style="display: none;"
>
+ <span>
+ Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the
+ <a
+ class="gl-link gl-display-inline-block"
+ href="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ GitLab documentation
+ </a>
+ to learn more about configuring your endpoint.
+ </span>
+
<fieldset
class="form-group gl-form-group"
id="integration-webhook"
diff --git a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
index f8e192dfae9..68a39b73eff 100644
--- a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js
@@ -43,10 +43,10 @@ describe('AlertsSettingsForm', () => {
});
};
- const findForm = () => wrapper.find(GlForm);
- const findSelect = () => wrapper.find(GlFormSelect);
- const findFormFields = () => wrapper.findAll(GlFormInput);
- const findFormToggle = () => wrapper.find(GlToggle);
+ const findForm = () => wrapper.findComponent(GlForm);
+ const findSelect = () => wrapper.findComponent(GlFormSelect);
+ const findFormFields = () => wrapper.findAllComponents(GlFormInput);
+ const findFormToggle = () => wrapper.findComponent(GlToggle);
const findSamplePayloadSection = () => wrapper.find('[data-testid="sample-payload-section"]');
const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`);
const findMappingBuilder = () => wrapper.findComponent(MappingBuilder);
@@ -56,7 +56,7 @@ describe('AlertsSettingsForm', () => {
const findJsonTestSubmit = () => wrapper.find(`[data-testid="send-test-alert"]`);
const findJsonTextArea = () => wrapper.find(`[id = "test-payload"]`);
const findActionBtn = () => wrapper.find(`[data-testid="payload-action-btn"]`);
- const findTabs = () => wrapper.findAll(GlTab);
+ const findTabs = () => wrapper.findAllComponents(GlTab);
afterEach(() => {
if (wrapper) {
diff --git a/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap b/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap
new file mode 100644
index 00000000000..5f05b7fc68b
--- /dev/null
+++ b/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap
@@ -0,0 +1,70 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Ref selector component footer slot passes the expected slot props 1`] = `
+Object {
+ "isLoading": false,
+ "matches": Object {
+ "branches": Object {
+ "error": null,
+ "list": Array [
+ Object {
+ "default": false,
+ "name": "add_images_and_changes",
+ },
+ Object {
+ "default": false,
+ "name": "conflict-contains-conflict-markers",
+ },
+ Object {
+ "default": false,
+ "name": "deleted-image-test",
+ },
+ Object {
+ "default": false,
+ "name": "diff-files-image-to-symlink",
+ },
+ Object {
+ "default": false,
+ "name": "diff-files-symlink-to-image",
+ },
+ Object {
+ "default": false,
+ "name": "markdown",
+ },
+ Object {
+ "default": true,
+ "name": "master",
+ },
+ ],
+ "totalCount": 123,
+ },
+ "commits": Object {
+ "error": null,
+ "list": Array [
+ Object {
+ "name": "b83d6e39",
+ "subtitle": "Merge branch 'branch-merged' into 'master'",
+ "value": "b83d6e391c22777fca1ed3012fce84f633d7fed0",
+ },
+ ],
+ "totalCount": 1,
+ },
+ "tags": Object {
+ "error": null,
+ "list": Array [
+ Object {
+ "name": "v1.1.1",
+ },
+ Object {
+ "name": "v1.1.0",
+ },
+ Object {
+ "name": "v1.0.0",
+ },
+ ],
+ "totalCount": 456,
+ },
+ },
+ "query": "abcd1234",
+}
+`;
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index cce365b1949..715c862cf39 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -1,7 +1,8 @@
-import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { merge, last } from 'lodash';
import Vuex from 'vuex';
import { trimText } from 'helpers/text_helper';
import { ENTER_KEY } from '~/lib/utils/keys';
@@ -34,26 +35,30 @@ describe('Ref selector component', () => {
let commitApiCallSpy;
let requestSpies;
- const createComponent = (props = {}, attrs = {}) => {
- wrapper = mount(RefSelector, {
- propsData: {
- projectId,
- value: '',
- ...props,
- },
- attrs,
- listeners: {
- // simulate a parent component v-model binding
- input: (selectedRef) => {
- wrapper.setProps({ value: selectedRef });
+ const createComponent = (mountOverrides = {}) => {
+ wrapper = mount(
+ RefSelector,
+ merge(
+ {
+ propsData: {
+ projectId,
+ value: '',
+ },
+ listeners: {
+ // simulate a parent component v-model binding
+ input: (selectedRef) => {
+ wrapper.setProps({ value: selectedRef });
+ },
+ },
+ stubs: {
+ GlSearchBoxByType: true,
+ },
+ localVue,
+ store: createStore(),
},
- },
- stubs: {
- GlSearchBoxByType: true,
- },
- localVue,
- store: createStore(),
- });
+ mountOverrides,
+ ),
+ );
};
beforeEach(() => {
@@ -183,7 +188,7 @@ describe('Ref selector component', () => {
const id = 'git-ref';
beforeEach(() => {
- createComponent({}, { id });
+ createComponent({ attrs: { id } });
return waitForRequests();
});
@@ -197,7 +202,7 @@ describe('Ref selector component', () => {
const preselectedRef = fixtures.branches[0].name;
beforeEach(() => {
- createComponent({ value: preselectedRef });
+ createComponent({ propsData: { value: preselectedRef } });
return waitForRequests();
});
@@ -611,7 +616,7 @@ describe('Ref selector component', () => {
`(
'only calls $reqsCalled requests when $enabledRefTypes are enabled',
async ({ enabledRefTypes, reqsCalled, reqsNotCalled }) => {
- createComponent({ enabledRefTypes });
+ createComponent({ propsData: { enabledRefTypes } });
await waitForRequests();
@@ -621,7 +626,7 @@ describe('Ref selector component', () => {
);
it('only calls commitApiCallSpy when REF_TYPE_COMMITS is enabled', async () => {
- createComponent({ enabledRefTypes: [REF_TYPE_COMMITS] });
+ createComponent({ propsData: { enabledRefTypes: [REF_TYPE_COMMITS] } });
updateQuery('abcd1234');
await waitForRequests();
@@ -632,7 +637,7 @@ describe('Ref selector component', () => {
});
it('triggers another search if enabled ref types change', async () => {
- createComponent({ enabledRefTypes: [REF_TYPE_BRANCHES] });
+ createComponent({ propsData: { enabledRefTypes: [REF_TYPE_BRANCHES] } });
await waitForRequests();
expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
@@ -648,7 +653,7 @@ describe('Ref selector component', () => {
});
it('if a ref type becomes disabled, its section is hidden, even if it had some results in store', async () => {
- createComponent({ enabledRefTypes: [REF_TYPE_BRANCHES, REF_TYPE_COMMITS] });
+ createComponent({ propsData: { enabledRefTypes: [REF_TYPE_BRANCHES, REF_TYPE_COMMITS] } });
updateQuery('abcd1234');
await waitForRequests();
@@ -670,7 +675,7 @@ describe('Ref selector component', () => {
`(
'hides section headers if a single ref type is enabled',
async ({ enabledRefType, findVisibleSection, findHiddenSections }) => {
- createComponent({ enabledRefTypes: [enabledRefType] });
+ createComponent({ propsData: { enabledRefTypes: [enabledRefType] } });
updateQuery('abcd1234');
await waitForRequests();
@@ -682,4 +687,70 @@ describe('Ref selector component', () => {
},
);
});
+
+ describe('validation state', () => {
+ const invalidClass = 'gl-inset-border-1-red-500!';
+ const isInvalidClassApplied = () => wrapper.find(GlDropdown).props('toggleClass')[invalidClass];
+
+ describe('valid state', () => {
+ describe('when the state prop is not provided', () => {
+ it('does not render a red border', () => {
+ createComponent();
+
+ expect(isInvalidClassApplied()).toBe(false);
+ });
+ });
+
+ describe('when the state prop is true', () => {
+ it('does not render a red border', () => {
+ createComponent({ propsData: { state: true } });
+
+ expect(isInvalidClassApplied()).toBe(false);
+ });
+ });
+ });
+
+ describe('invalid state', () => {
+ it('renders the dropdown with a red border if the state prop is false', () => {
+ createComponent({ propsData: { state: false } });
+
+ expect(isInvalidClassApplied()).toBe(true);
+ });
+ });
+ });
+
+ describe('footer slot', () => {
+ const footerContent = 'This is the footer content';
+ const createFooter = jest.fn().mockImplementation(function createMockFooter() {
+ return this.$createElement('div', { attrs: { 'data-testid': 'footer-content' } }, [
+ footerContent,
+ ]);
+ });
+
+ beforeEach(() => {
+ createComponent({
+ scopedSlots: { footer: createFooter },
+ });
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ afterEach(() => {
+ createFooter.mockClear();
+ });
+
+ it('allows custom content to be shown at the bottom of the dropdown using the footer slot', () => {
+ expect(wrapper.find(`[data-testid="footer-content"]`).text()).toBe(footerContent);
+ });
+
+ it('passes the expected slot props', () => {
+ // The createFooter function gets called every time one of the scoped properties
+ // is updated. For the sake of this test, we'll just test the last call, which
+ // represents the final state of the slot props.
+ const lastCallProps = last(createFooter.mock.calls)[0];
+ expect(lastCallProps).toMatchSnapshot();
+ });
+ });
});
diff --git a/spec/frontend/single_file_diff_spec.js b/spec/frontend/single_file_diff_spec.js
new file mode 100644
index 00000000000..8718152655f
--- /dev/null
+++ b/spec/frontend/single_file_diff_spec.js
@@ -0,0 +1,96 @@
+import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
+import { setHTMLFixture } from 'helpers/fixtures';
+import axios from '~/lib/utils/axios_utils';
+import SingleFileDiff from '~/single_file_diff';
+
+describe('SingleFileDiff', () => {
+ let mock = new MockAdapter(axios);
+ const blobDiffPath = '/mock-path';
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(blobDiffPath).replyOnce(200, { html: `<div class="diff-content">MOCKED</div>` });
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('loads diff via axios exactly once for collapsed diffs', async () => {
+ setHTMLFixture(`
+ <div class="diff-file">
+ <div class="js-file-title">
+ MOCK TITLE
+ </div>
+
+ <div class="diff-content">
+ <div class="diff-viewer" data-type="simple">
+ <div
+ class="nothing-here-block diff-collapsed"
+ data-diff-for-path="${blobDiffPath}"
+ >
+ MOCK CONTENT
+ </div>
+ </div>
+ </div>
+ </div>
+`);
+
+ // Collapsed is the default state
+ const diff = new SingleFileDiff(document.querySelector('.diff-file'));
+ expect(diff.isOpen).toBe(false);
+ expect(diff.content).toBeNull();
+ expect(diff.diffForPath).toEqual(blobDiffPath);
+
+ // Opening for the first time
+ await diff.toggleDiff($(document.querySelector('.js-file-title')));
+ expect(diff.isOpen).toBe(true);
+ expect(diff.content).not.toBeNull();
+
+ // Collapsing again
+ await diff.toggleDiff($(document.querySelector('.js-file-title')));
+ expect(diff.isOpen).toBe(false);
+ expect(diff.content).not.toBeNull();
+
+ mock.onGet(blobDiffPath).replyOnce(400, '');
+
+ // Opening again
+ await diff.toggleDiff($(document.querySelector('.js-file-title')));
+ expect(diff.isOpen).toBe(true);
+ expect(diff.content).not.toBeNull();
+
+ expect(mock.history.get.length).toBe(1);
+ });
+
+ it('does not load diffs via axios for already expanded diffs', async () => {
+ setHTMLFixture(`
+ <div class="diff-file">
+ <div class="js-file-title">
+ MOCK TITLE
+ </div>
+
+ <div class="diff-content">
+ EXPANDED MOCK CONTENT
+ </div>
+ </div>
+`);
+
+ // Opened is the default state
+ const diff = new SingleFileDiff(document.querySelector('.diff-file'));
+ expect(diff.isOpen).toBe(true);
+ expect(diff.content).not.toBeNull();
+ expect(diff.diffForPath).toEqual(undefined);
+
+ // Collapsing for the first time
+ await diff.toggleDiff($(document.querySelector('.js-file-title')));
+ expect(diff.isOpen).toBe(false);
+ expect(diff.content).not.toBeNull();
+
+ // Opening again
+ await diff.toggleDiff($(document.querySelector('.js-file-title')));
+ expect(diff.isOpen).toBe(true);
+
+ expect(mock.history.get.length).toBe(0);
+ });
+});
diff --git a/spec/graphql/mutations/user_callouts/create_spec.rb b/spec/graphql/mutations/user_callouts/create_spec.rb
new file mode 100644
index 00000000000..93f227d8b82
--- /dev/null
+++ b/spec/graphql/mutations/user_callouts/create_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::UserCallouts::Create do
+ let(:current_user) { create(:user) }
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation.resolve(feature_name: feature_name) }
+
+ context 'when feature name is not supported' do
+ let(:feature_name) { 'not_supported' }
+
+ it 'does not create a user callout' do
+ expect { resolve }.not_to change(UserCallout, :count).from(0)
+ end
+
+ it 'returns error about feature name not being supported' do
+ expect(resolve[:errors]).to include("Feature name is not included in the list")
+ end
+ end
+
+ context 'when feature name is supported' do
+ let(:feature_name) { UserCallout.feature_names.each_key.first.to_s }
+
+ it 'creates a user callout' do
+ expect { resolve }.to change(UserCallout, :count).from(0).to(1)
+ end
+
+ it 'sets dismissed_at for the user callout' do
+ freeze_time do
+ expect(resolve[:user_callout].dismissed_at).to eq(Time.current)
+ end
+ end
+
+ it 'has no errors' do
+ expect(resolve[:errors]).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/user_callout_feature_name_enum_spec.rb b/spec/graphql/types/user_callout_feature_name_enum_spec.rb
new file mode 100644
index 00000000000..28755e1301b
--- /dev/null
+++ b/spec/graphql/types/user_callout_feature_name_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['UserCalloutFeatureNameEnum'] do
+ specify { expect(described_class.graphql_name).to eq('UserCalloutFeatureNameEnum') }
+
+ it 'exposes all the existing user callout feature names' do
+ expect(described_class.values.keys).to match_array(::UserCallout.feature_names.keys.map(&:upcase))
+ end
+end
diff --git a/spec/graphql/types/user_callout_type_spec.rb b/spec/graphql/types/user_callout_type_spec.rb
new file mode 100644
index 00000000000..b26b85a4e8b
--- /dev/null
+++ b/spec/graphql/types/user_callout_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['UserCallout'] do
+ specify { expect(described_class.graphql_name).to eq('UserCallout') }
+
+ it 'has expected fields' do
+ expect(described_class).to have_graphql_fields(:feature_name, :dismissed_at)
+ end
+end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 5b3662383d8..d9e67ff348b 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe GitlabSchema.types['User'] do
groupCount
projectMemberships
starredProjects
+ callouts
]
expect(described_class).to have_graphql_fields(*expected_fields)
@@ -44,4 +45,12 @@ RSpec.describe GitlabSchema.types['User'] do
is_expected.to have_graphql_resolver(Resolvers::Users::SnippetsResolver)
end
end
+
+ describe 'callouts field' do
+ subject { described_class.fields['callouts'] }
+
+ it 'returns user callouts' do
+ is_expected.to have_graphql_type(Types::UserCalloutType.connection_type)
+ end
+ end
end
diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb
new file mode 100644
index 00000000000..db30446fa95
--- /dev/null
+++ b/spec/helpers/ide_helper_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IdeHelper do
+ describe '#ide_data' do
+ let_it_be(:project) { create(:project) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(project.creator)
+ end
+
+ context 'when instance vars are not set' do
+ it 'returns instance data in the hash as nil' do
+ expect(helper.ide_data)
+ .to include(
+ 'branch-name' => nil,
+ 'file-path' => nil,
+ 'merge-request' => nil,
+ 'forked-project' => nil,
+ 'project' => nil
+ )
+ end
+ end
+
+ context 'when instance vars are set' do
+ it 'returns instance data in the hash' do
+ self.instance_variable_set(:@branch, 'master')
+ self.instance_variable_set(:@path, 'foo/bar')
+ self.instance_variable_set(:@merge_request, '1')
+ self.instance_variable_set(:@forked_project, project)
+ self.instance_variable_set(:@project, project)
+
+ serialized_project = API::Entities::Project.represent(project).to_json
+
+ expect(helper.ide_data)
+ .to include(
+ 'branch-name' => 'master',
+ 'file-path' => 'foo/bar',
+ 'merge-request' => '1',
+ 'forked-project' => serialized_project,
+ 'project' => serialized_project
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/docs/renderer_spec.rb b/spec/lib/gitlab/usage/docs/renderer_spec.rb
index 07f25cfcfa7..f3b83a4a4b3 100644
--- a/spec/lib/gitlab/usage/docs/renderer_spec.rb
+++ b/spec/lib/gitlab/usage/docs/renderer_spec.rb
@@ -2,6 +2,8 @@
require 'spec_helper'
+CODE_REGEX = %r{<code>(.*)</code>}.freeze
+
RSpec.describe Gitlab::Usage::Docs::Renderer do
describe 'contents' do
let(:dictionary_path) { Gitlab::Usage::Docs::Renderer::DICTIONARY_PATH }
@@ -9,12 +11,12 @@ RSpec.describe Gitlab::Usage::Docs::Renderer do
it 'generates dictionary for given items' do
generated_dictionary = described_class.new(items).contents
+
generated_dictionary_keys = RDoc::Markdown
.parse(generated_dictionary)
.table_of_contents
- .select { |metric_doc| metric_doc.level == 2 && !metric_doc.text.start_with?('info:') }
- .map(&:text)
- .map { |text| text.sub('<code>', '').sub('</code>', '') }
+ .select { |metric_doc| metric_doc.level == 3 }
+ .map { |item| item.text.match(CODE_REGEX)&.captures&.first }
expect(generated_dictionary_keys).to match_array(items.keys)
end
diff --git a/spec/lib/gitlab/usage/docs/value_formatter_spec.rb b/spec/lib/gitlab/usage/docs/value_formatter_spec.rb
index 7002c76a7cf..7e7107ef1a6 100644
--- a/spec/lib/gitlab/usage/docs/value_formatter_spec.rb
+++ b/spec/lib/gitlab/usage/docs/value_formatter_spec.rb
@@ -10,11 +10,11 @@ RSpec.describe Gitlab::Usage::Docs::ValueFormatter do
:data_source | 'redis' | 'Redis'
:data_source | 'ruby' | 'Ruby'
:introduced_by_url | 'http://test.com' | '[Introduced by](http://test.com)'
- :tier | %w(gold premium) | 'gold, premium'
- :distribution | %w(ce ee) | 'ce, ee'
+ :tier | %w(gold premium) | '`gold`, `premium`'
+ :distribution | %w(ce ee) | '`ce`, `ee`'
:key_path | 'key.path' | '**`key.path`**'
:milestone | '13.4' | '13.4'
- :status | 'data_available' | 'data_available'
+ :status | 'data_available' | '`data_available`'
end
with_them do
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
index 717c3b8eca4..d0c3456aa26 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -21,6 +21,14 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
end
end
+ shared_examples_for 'not tracked merge request unique event' do
+ specify do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ subject
+ end
+ end
+
describe '.track_mr_diffs_action' do
subject { described_class.track_mr_diffs_action(merge_request: merge_request) }
@@ -332,4 +340,34 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
let(:action) { described_class::MR_REVIEWERS_CHANGED_ACTION }
end
end
+
+ describe '.track_mr_including_ci_config' do
+ subject { described_class.track_mr_including_ci_config(user: user, merge_request: merge_request) }
+
+ context 'when merge request includes a ci config change' do
+ before do
+ allow(merge_request).to receive(:diff_stats).and_return([double(path: 'abc.txt'), double(path: '.gitlab-ci.yml')])
+ end
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_INCLUDING_CI_CONFIG_ACTION }
+ end
+
+ context 'when FF usage_data_o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile is disabled' do
+ before do
+ stub_feature_flags(usage_data_o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile: false)
+ end
+
+ it_behaves_like 'not tracked merge request unique event'
+ end
+ end
+
+ context 'when merge request does not include any ci config change' do
+ before do
+ allow(merge_request).to receive(:diff_stats).and_return([double(path: 'abc.txt'), double(path: 'abc.xyz')])
+ end
+
+ it_behaves_like 'not tracked merge request unique event'
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 1aa2cbb5485..7b7046b1d77 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1353,7 +1353,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
let(:ineligible_total_categories) do
- %w[source_code ci_secrets_management incident_management_alerts snippets terraform pipeline_authoring]
+ %w[source_code ci_secrets_management incident_management_alerts snippets terraform]
end
it 'has all known_events' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index f6955ded748..31f4310fd63 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -581,7 +581,7 @@ RSpec.describe Ci::Build do
end
it 'that cannot handle build' do
- expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
+ expect_any_instance_of(Ci::Runner).to receive(:matches_build?).with(build).and_return(false)
is_expected.to be_falsey
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 4c8212bf774..ff3551d2a18 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -350,6 +350,8 @@ RSpec.describe Ci::Runner do
end
describe '#can_pick?' do
+ using RSpec::Parameterized::TableSyntax
+
let_it_be(:pipeline) { create(:ci_pipeline) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:runner_project) { build.project }
@@ -363,6 +365,11 @@ RSpec.describe Ci::Runner do
let(:other_project) { create(:project) }
let(:other_runner) { create(:ci_runner, :project, projects: [other_project], tag_list: tag_list, run_untagged: run_untagged) }
+ before do
+ # `can_pick?` is not used outside the runners available for the project
+ stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
+ end
+
it 'cannot handle builds' do
expect(other_runner.can_pick?(build)).to be_falsey
end
@@ -430,9 +437,32 @@ RSpec.describe Ci::Runner do
expect(runner.can_pick?(build)).to be_truthy
end
end
+
+ it 'does not query for owned or instance runners' do
+ expect(described_class).not_to receive(:owned_or_instance_wide)
+
+ runner.can_pick?(build)
+ end
+
+ context 'when feature flag ci_runners_short_circuit_assignable_for is disabled' do
+ before do
+ stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
+ end
+
+ it 'does not query for owned or instance runners' do
+ expect(described_class).to receive(:owned_or_instance_wide).and_call_original
+
+ runner.can_pick?(build)
+ end
+ end
end
context 'when runner is not shared' do
+ before do
+ # `can_pick?` is not used outside the runners available for the project
+ stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
+ end
+
context 'when runner is assigned to a project' do
it 'can handle builds' do
expect(runner.can_pick?(build)).to be_truthy
@@ -500,6 +530,29 @@ RSpec.describe Ci::Runner do
it { is_expected.to be_falsey }
end
end
+
+ context 'matches tags' do
+ where(:run_untagged, :runner_tags, :build_tags, :result) do
+ true | [] | [] | true
+ true | [] | ['a'] | false
+ true | %w[a b] | ['a'] | true
+ true | ['a'] | %w[a b] | false
+ true | ['a'] | ['a'] | true
+ false | ['a'] | ['a'] | true
+ false | ['b'] | ['a'] | false
+ false | %w[a b] | ['a'] | true
+ end
+
+ with_them do
+ let(:tag_list) { runner_tags }
+
+ before do
+ build.tag_list = build_tags
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
describe '#status' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 551da6d0471..1ff6c7c5b53 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -4856,4 +4856,33 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
end
+
+ describe '#includes_ci_config?' do
+ let(:merge_request) { build(:merge_request) }
+ let(:project) { merge_request.project }
+
+ subject(:result) { merge_request.includes_ci_config? }
+
+ before do
+ allow(merge_request).to receive(:diff_stats).and_return(diff_stats)
+ end
+
+ context 'when diff_stats is nil' do
+ let(:diff_stats) {}
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when diff_stats does not include the ci config path of the project' do
+ let(:diff_stats) { [double(path: 'abc.txt')] }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when diff_stats includes the ci config path of the project' do
+ let(:diff_stats) { [double(path: '.gitlab-ci.yml')] }
+
+ it { is_expected.to eq(true) }
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 6d0f01d2c8c..b4010ba6e31 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1599,7 +1599,7 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#any_runners?' do
+ describe '#any_active_runners?' do
context 'shared runners' do
let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) }
let(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
@@ -1609,31 +1609,31 @@ RSpec.describe Project, factory_default: :keep do
let(:shared_runners_enabled) { false }
it 'has no runners available' do
- expect(project.any_runners?).to be_falsey
+ expect(project.any_active_runners?).to be_falsey
end
it 'has a specific runner' do
specific_runner
- expect(project.any_runners?).to be_truthy
+ expect(project.any_active_runners?).to be_truthy
end
it 'has a shared runner, but they are prohibited to use' do
shared_runner
- expect(project.any_runners?).to be_falsey
+ expect(project.any_active_runners?).to be_falsey
end
it 'checks the presence of specific runner' do
specific_runner
- expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
+ expect(project.any_active_runners? { |runner| runner == specific_runner }).to be_truthy
end
it 'returns false if match cannot be found' do
specific_runner
- expect(project.any_runners? { false }).to be_falsey
+ expect(project.any_active_runners? { false }).to be_falsey
end
end
@@ -1643,19 +1643,19 @@ RSpec.describe Project, factory_default: :keep do
it 'has a shared runner' do
shared_runner
- expect(project.any_runners?).to be_truthy
+ expect(project.any_active_runners?).to be_truthy
end
it 'checks the presence of shared runner' do
shared_runner
- expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
+ expect(project.any_active_runners? { |runner| runner == shared_runner }).to be_truthy
end
it 'returns false if match cannot be found' do
shared_runner
- expect(project.any_runners? { false }).to be_falsey
+ expect(project.any_active_runners? { false }).to be_falsey
end
end
end
@@ -1669,13 +1669,13 @@ RSpec.describe Project, factory_default: :keep do
let(:group_runners_enabled) { false }
it 'has no runners available' do
- expect(project.any_runners?).to be_falsey
+ expect(project.any_active_runners?).to be_falsey
end
it 'has a group runner, but they are prohibited to use' do
group_runner
- expect(project.any_runners?).to be_falsey
+ expect(project.any_active_runners?).to be_falsey
end
end
@@ -1685,19 +1685,19 @@ RSpec.describe Project, factory_default: :keep do
it 'has a group runner' do
group_runner
- expect(project.any_runners?).to be_truthy
+ expect(project.any_active_runners?).to be_truthy
end
it 'checks the presence of group runner' do
group_runner
- expect(project.any_runners? { |runner| runner == group_runner }).to be_truthy
+ expect(project.any_active_runners? { |runner| runner == group_runner }).to be_truthy
end
it 'returns false if match cannot be found' do
group_runner
- expect(project.any_runners? { false }).to be_falsey
+ expect(project.any_active_runners? { false }).to be_falsey
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index c1e1d878e95..94e83d224e6 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -5482,4 +5482,43 @@ RSpec.describe User do
end
end
end
+
+ describe '#find_or_initialize_callout' do
+ subject(:find_or_initialize_callout) { user.find_or_initialize_callout(feature_name) }
+
+ let(:user) { create(:user) }
+ let(:feature_name) { UserCallout.feature_names.each_key.first }
+
+ context 'when callout exists' do
+ let!(:callout) { create(:user_callout, user: user, feature_name: feature_name) }
+
+ it 'returns existing callout' do
+ expect(find_or_initialize_callout).to eq(callout)
+ end
+ end
+
+ context 'when callout does not exist' do
+ context 'when feature name is valid' do
+ it 'initializes a new callout' do
+ expect(find_or_initialize_callout).to be_a_new(UserCallout)
+ end
+
+ it 'is valid' do
+ expect(find_or_initialize_callout).to be_valid
+ end
+ end
+
+ context 'when feature name is not valid' do
+ let(:feature_name) { 'notvalid' }
+
+ it 'initializes a new callout' do
+ expect(find_or_initialize_callout).to be_a_new(UserCallout)
+ end
+
+ it 'is not valid' do
+ expect(find_or_initialize_callout).not_to be_valid
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb b/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb
new file mode 100644
index 00000000000..cb67a60ebe4
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create a user callout' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let(:feature_name) { ::UserCallout.feature_names.each_key.first }
+
+ let(:input) do
+ {
+ 'featureName' => feature_name
+ }
+ end
+
+ let(:mutation) { graphql_mutation(:userCalloutCreate, input) }
+ let(:mutation_response) { graphql_mutation_response(:userCalloutCreate) }
+
+ it 'creates user callout' do
+ freeze_time do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['userCallout']['featureName']).to eq(feature_name.upcase)
+ expect(mutation_response['userCallout']['dismissedAt']).to eq(Time.current.iso8601)
+ end
+ end
+end
diff --git a/spec/requests/ide_controller_spec.rb b/spec/requests/ide_controller_spec.rb
index 805c1f1d82b..4f127e07b6b 100644
--- a/spec/requests/ide_controller_spec.rb
+++ b/spec/requests/ide_controller_spec.rb
@@ -3,7 +3,11 @@
require 'spec_helper'
RSpec.describe IdeController do
- let(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:creator) { project.creator }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:user) { creator }
before do
sign_in(user)
@@ -14,4 +18,172 @@ RSpec.describe IdeController do
get ide_url
end
+
+ describe '#index', :aggregate_failures do
+ subject { get route }
+
+ shared_examples 'user cannot push code' do
+ include ProjectForksHelper
+
+ let(:user) { other_user }
+
+ context 'when user does not have fork' do
+ it 'does not instantiate forked_project instance var and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:forked_project)).to be_nil
+ end
+ end
+
+ context 'when user has have fork' do
+ let!(:fork) { fork_project(project, user, repository: true) }
+
+ it 'instantiates forked_project instance var and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:forked_project)).to eq fork
+ end
+ end
+ end
+
+ context '/-/ide' do
+ let(:route) { '/-/ide' }
+
+ it 'does not instantiate any instance var and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to be_nil
+ expect(assigns(:branch)).to be_nil
+ expect(assigns(:path)).to be_nil
+ expect(assigns(:merge_request)).to be_nil
+ expect(assigns(:forked_project)).to be_nil
+ end
+ end
+
+ context '/-/ide/project' do
+ let(:route) { '/-/ide/project' }
+
+ it 'does not instantiate any instance var and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to be_nil
+ expect(assigns(:branch)).to be_nil
+ expect(assigns(:path)).to be_nil
+ expect(assigns(:merge_request)).to be_nil
+ expect(assigns(:forked_project)).to be_nil
+ end
+ end
+
+ context '/-/ide/project/:project' do
+ let(:route) { "/-/ide/project/#{project.full_path}" }
+
+ it 'instantiates project instance var and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:branch)).to be_nil
+ expect(assigns(:path)).to be_nil
+ expect(assigns(:merge_request)).to be_nil
+ expect(assigns(:forked_project)).to be_nil
+ end
+
+ it_behaves_like 'user cannot push code'
+
+ %w(edit blob tree).each do |action|
+ context "/-/ide/project/:project/#{action}" do
+ let(:route) { "/-/ide/project/#{project.full_path}/#{action}" }
+
+ it 'instantiates project instance var and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:branch)).to be_nil
+ expect(assigns(:path)).to be_nil
+ expect(assigns(:merge_request)).to be_nil
+ expect(assigns(:forked_project)).to be_nil
+ end
+
+ it_behaves_like 'user cannot push code'
+
+ context "/-/ide/project/:project/#{action}/:branch" do
+ let(:route) { "/-/ide/project/#{project.full_path}/#{action}/master" }
+
+ it 'instantiates project and branch instance vars and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:branch)).to eq 'master'
+ expect(assigns(:path)).to be_nil
+ expect(assigns(:merge_request)).to be_nil
+ expect(assigns(:forked_project)).to be_nil
+ end
+
+ it_behaves_like 'user cannot push code'
+
+ context "/-/ide/project/:project/#{action}/:branch/-" do
+ let(:route) { "/-/ide/project/#{project.full_path}/#{action}/branch/slash/-" }
+
+ it 'instantiates project and branch instance vars and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:branch)).to eq 'branch/slash'
+ expect(assigns(:path)).to be_nil
+ expect(assigns(:merge_request)).to be_nil
+ expect(assigns(:forked_project)).to be_nil
+ end
+
+ it_behaves_like 'user cannot push code'
+
+ context "/-/ide/project/:project/#{action}/:branch/-/:path" do
+ let(:route) { "/-/ide/project/#{project.full_path}/#{action}/master/-/foo/.bar" }
+
+ it 'instantiates project, branch, and path instance vars and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:branch)).to eq 'master'
+ expect(assigns(:path)).to eq 'foo/.bar'
+ expect(assigns(:merge_request)).to be_nil
+ expect(assigns(:forked_project)).to be_nil
+ end
+
+ it_behaves_like 'user cannot push code'
+ end
+ end
+ end
+ end
+ end
+
+ context '/-/ide/project/:project/merge_requests/:merge_request_id' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+ let(:route) { "/-/ide/project/#{project.full_path}/merge_requests/#{merge_request.id}" }
+
+ it 'instantiates project and merge_request instance vars and return 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:project)).to eq project
+ expect(assigns(:branch)).to be_nil
+ expect(assigns(:path)).to be_nil
+ expect(assigns(:merge_request)).to eq merge_request.id.to_s
+ expect(assigns(:forked_project)).to be_nil
+ end
+
+ it_behaves_like 'user cannot push code'
+ end
+ end
+ end
end
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index 6cac49febda..2d9f80a249d 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -36,6 +36,7 @@ RSpec.describe Ci::UpdateBuildQueueService do
context 'when feature flag ci_reduce_queries_when_ticking_runner_queue is disabled' do
before do
stub_feature_flags(ci_reduce_queries_when_ticking_runner_queue: false)
+ stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
end
it 'runs redundant queries using `owned_or_instance_wide` scope' do
diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb
index f21feb70bc5..65dd4fde676 100644
--- a/spec/services/merge_requests/after_create_service_spec.rb
+++ b/spec/services/merge_requests/after_create_service_spec.rb
@@ -32,6 +32,10 @@ RSpec.describe MergeRequests::AfterCreateService do
.to receive(:track_create_mr_action)
.with(user: merge_request.author)
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .to receive(:track_mr_including_ci_config)
+ .with(user: merge_request.author, merge_request: merge_request)
+
execute_service
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index eeb782b573e..2abe7a23bfe 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -170,6 +170,18 @@ RSpec.describe MergeRequests::RefreshService do
.not_to change { @merge_request.reload.merge_request_diff }
end
end
+
+ it 'calls the merge request activity counter' do
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .to receive(:track_mr_including_ci_config)
+ .with(user: @merge_request.author, merge_request: @merge_request)
+
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .to receive(:track_mr_including_ci_config)
+ .with(user: @another_merge_request.author, merge_request: @another_merge_request)
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ end
end
context 'when pipeline exists for the source branch' do
diff --git a/spec/services/users/dismiss_user_callout_service_spec.rb b/spec/services/users/dismiss_user_callout_service_spec.rb
new file mode 100644
index 00000000000..22f84a939f7
--- /dev/null
+++ b/spec/services/users/dismiss_user_callout_service_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::DismissUserCalloutService do
+ let(:user) { create(:user) }
+
+ let(:service) do
+ described_class.new(
+ container: nil, current_user: user, params: { feature_name: UserCallout.feature_names.each_key.first }
+ )
+ end
+
+ describe '#execute' do
+ subject(:execute) { service.execute }
+
+ it 'returns a user callout' do
+ expect(execute).to be_an_instance_of(UserCallout)
+ end
+
+ it 'sets the dismisse_at attribute to current time' do
+ freeze_time do
+ expect(execute).to have_attributes(dismissed_at: Time.current)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
index 4411c91d479..7daebcbb0fe 100644
--- a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
@@ -94,3 +94,23 @@ RSpec.shared_examples 'it uploads and commit a new file to a forked project' do
expect(page).to have_content('Sed ut perspiciatis unde omnis')
end
end
+
+RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do
+ it 'uploads and commits a new text file via "upload file" button', :js do
+ find('.js-upload-file-experiment-trigger', text: 'Upload file').click
+
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'New commit message')
+ end
+
+ click_button('Upload file')
+
+ wait_for_requests
+
+ expect(page).to have_content('New commit message')
+ expect(page).to have_content('Lorem ipsum dolor sit amet')
+ expect(page).to have_content('Sed ut perspiciatis unde omnis')
+ end
+end