diff options
Diffstat (limited to 'spec')
33 files changed, 951 insertions, 163 deletions
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index ab185ab3972..743ec322885 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -260,6 +260,7 @@ FactoryBot.define do trait(:merge_requests_enabled) { merge_requests_access_level ProjectFeature::ENABLED } trait(:merge_requests_disabled) { merge_requests_access_level ProjectFeature::DISABLED } trait(:merge_requests_private) { merge_requests_access_level ProjectFeature::PRIVATE } + trait(:merge_requests_public) { merge_requests_access_level ProjectFeature::PUBLIC } trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED } trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED } trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE } diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index e0553086fd7..5b17c49db2d 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe 'Dashboard > User filters projects' do let(:user) { create(:user) } - let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace) } + let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace, created_at: 2.seconds.ago, updated_at: 2.seconds.ago) } let(:user2) { create(:user) } - let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace) } + let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace, created_at: 1.second.ago, updated_at: 1.second.ago) } before do project.add_maintainer(user) @@ -129,7 +129,7 @@ describe 'Dashboard > User filters projects' do list = page.all('.projects-list .project-name').map(&:text) - expect(list).to match(["Private project", "Treasure", "Victorialand"]) + expect(list).to contain_exactly("Private project", "Treasure", "Victorialand") end it 'filters internal projects only' do @@ -139,14 +139,14 @@ describe 'Dashboard > User filters projects' do list = page.all('.projects-list .project-name').map(&:text) - expect(list).to match(['Internal project']) + expect(list).to contain_exactly('Internal project') end it 'filters any project' do select_dropdown_option '#filtered-search-visibility-dropdown', 'Any' list = page.all('.projects-list .project-name').map(&:text) - expect(list).to match(["Internal project", "Private project", "Treasure", "Victorialand"]) + expect(list).to contain_exactly("Internal project", "Private project", "Treasure", "Victorialand") end end diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 40ba676ff92..a32c6bdcf8f 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -670,4 +670,26 @@ describe 'Merge request > User sees merge widget', :js do end end end + + context 'when MR has pipeline but user does not have permission' do + let(:sha) { project.commit(merge_request.source_branch).sha } + let!(:pipeline) { create(:ci_pipeline_without_jobs, status: 'success', sha: sha, project: project, ref: merge_request.source_branch) } + + before do + project.update( + visibility_level: Gitlab::VisibilityLevel::PUBLIC, + public_builds: false + ) + merge_request.update!(head_pipeline: pipeline) + sign_out(:user) + + visit project_merge_request_path(project, merge_request) + end + + it 'renders a CI pipeline error' do + within '.ci-widget' do + expect(page).to have_content('Could not retrieve the pipeline status.') + end + end + end end diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index 1b5dd6945e0..04c7f4b6c76 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -121,7 +121,7 @@ describe 'User comments on a diff', :js do end context 'multi-line suggestions' do - it 'suggestion is presented' do + before do click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) page.within('.js-discussion-note-form') do @@ -130,7 +130,9 @@ describe 'User comments on a diff', :js do end wait_for_requests + end + it 'suggestion is presented' do page.within('.diff-discussions') do expect(page).to have_button('Apply suggestion') expect(page).to have_content('Suggested change') @@ -160,22 +162,24 @@ describe 'User comments on a diff', :js do end it 'suggestion is appliable' do - click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + page.within('.diff-discussions') do + expect(page).not_to have_content('Applied') - page.within('.js-discussion-note-form') do - fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```") - click_button('Comment') - end + click_button('Apply suggestion') + wait_for_requests - wait_for_requests + expect(page).to have_content('Applied') + end + end + it 'resolves discussion when applied' do page.within('.diff-discussions') do - expect(page).not_to have_content('Applied') + expect(page).not_to have_content('Unresolve discussion') click_button('Apply suggestion') wait_for_requests - expect(page).to have_content('Applied') + expect(page).to have_content('Unresolve discussion') end end end diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb index f4105730402..5ebfc32952d 100644 --- a/spec/features/oauth_login_spec.rb +++ b/spec/features/oauth_login_spec.rb @@ -14,7 +14,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do end providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2, - :facebook, :cas3, :auth0, :authentiq] + :facebook, :cas3, :auth0, :authentiq, :salesforce] before(:all) do # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost` diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb new file mode 100644 index 00000000000..2d2da222998 --- /dev/null +++ b/spec/features/profiles/user_edit_preferences_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'User edit preferences profile' do + let(:user) { create(:user) } + + before do + stub_feature_flags(user_time_settings: true) + sign_in(user) + visit(profile_preferences_path) + end + + it 'allows the user to toggle their time format preference' do + field = page.find_field("user[time_format_in_24h]") + + expect(field).not_to be_checked + + field.click + + expect(field).to be_checked + end + + it 'allows the user to toggle their time display preference' do + field = page.find_field("user[time_display_relative]") + + expect(field).to be_checked + + field.click + + expect(field).not_to be_checked + end +end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index b43711f6ef6..a53da94ef7d 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -327,5 +327,37 @@ describe 'User edit profile' do end end end + + context 'User time preferences', :js do + let(:issue) { create(:issue, project: project)} + let(:project) { create(:project) } + + before do + stub_feature_flags(user_time_settings: true) + end + + it 'shows the user time preferences form' do + expect(page).to have_content('Time settings') + end + + it 'allows the user to select a time zone from a dropdown list of options' do + expect(page.find('.user-time-preferences .dropdown')).not_to have_css('.show') + + page.find('.user-time-preferences .js-timezone-dropdown').click + + expect(page.find('.user-time-preferences .dropdown')).to have_css('.show') + + page.find("a", text: "Nuku'alofa").click + + tz = page.find('.user-time-preferences #user_timezone', visible: false) + + expect(tz.value).to eq('Pacific/Tongatapu') + end + + it 'timezone defaults to servers default' do + timezone_name = Time.zone.tzinfo.name + expect(page.find('.user-time-preferences #user_timezone', visible: false).value).to eq(timezone_name) + end + end end end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index f7de769cee9..8c7bc192c50 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -236,5 +236,17 @@ describe 'Projects > Settings > Repository settings' do expect(mirrored_project.remote_mirrors.count).to eq(0) end end + + it 'shows a disabled mirror' do + create(:remote_mirror, project: project, enabled: false) + + visit project_settings_repository_path(project) + + mirror = find('.qa-mirrored-repository-row') + + expect(mirror).to have_selector('.qa-delete-mirror') + expect(mirror).to have_selector('.qa-disabled-mirror-badge') + expect(mirror).not_to have_selector('.qa-update-now-button') + end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 6a47cd013f8..89fdaceaa9f 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -641,9 +641,7 @@ describe IssuesFinder do end it 'filters by confidentiality' do - expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) - - subject + expect(subject.to_sql).to match("issues.confidential") end end @@ -660,9 +658,7 @@ describe IssuesFinder do end it 'filters by confidentiality' do - expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) - - subject + expect(subject.to_sql).to match("issues.confidential") end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 117f4a03735..da5e9dab058 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -31,7 +31,7 @@ describe MergeRequestsFinder do end context 'filtering by group' do - it 'includes all merge requests when user has access exceluding merge requests from projects the user does not have access to' do + it 'includes all merge requests when user has access excluding merge requests from projects the user does not have access to' do private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) } private_project.add_guest(user) create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) diff --git a/spec/frontend/notes/stores/utils_spec.js b/spec/frontend/notes/stores/utils_spec.js new file mode 100644 index 00000000000..b31b7491334 --- /dev/null +++ b/spec/frontend/notes/stores/utils_spec.js @@ -0,0 +1,17 @@ +import { hasQuickActions } from '~/notes/stores/utils'; + +describe('hasQuickActions', () => { + it.each` + input | expected + ${'some comment'} | ${false} + ${'/quickaction'} | ${true} + ${'some comment with\n/quickaction'} | ${true} + `('returns $expected for $input', ({ input, expected }) => { + expect(hasQuickActions(input)).toBe(expected); + }); + + it('is stateless', () => { + expect(hasQuickActions('some comment')).toBe(hasQuickActions('some comment')); + expect(hasQuickActions('/quickaction')).toBe(hasQuickActions('/quickaction')); + }); +}); diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js new file mode 100644 index 00000000000..de1dd219fe0 --- /dev/null +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -0,0 +1,100 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue'; +import { TEST_HOST } from 'helpers/test_constants'; + +describe('operation settings external dashboard component', () => { + let wrapper; + const externalDashboardPath = `http://mock-external-domain.com/external/dashboard/path`; + const externalDashboardHelpPagePath = `${TEST_HOST}/help/page/path`; + + beforeEach(() => { + wrapper = shallowMount(ExternalDashboard, { + propsData: { + externalDashboardPath, + externalDashboardHelpPagePath, + }, + }); + }); + + it('renders header text', () => { + expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard'); + }); + + describe('sub-header', () => { + let subHeader; + + beforeEach(() => { + subHeader = wrapper.find('.js-section-sub-header'); + }); + + it('renders descriptive text', () => { + expect(subHeader.text()).toContain( + 'Add a button to the metrics dashboard linking directly to your existing external dashboards.', + ); + }); + + it('renders help page link', () => { + const link = subHeader.find(GlLink); + + expect(link.text()).toBe('Learn more'); + expect(link.attributes().href).toBe(externalDashboardHelpPagePath); + }); + }); + + describe('form', () => { + let form; + + beforeEach(() => { + form = wrapper.find('form'); + }); + + describe('external dashboard url', () => { + describe('input label', () => { + let formGroup; + + beforeEach(() => { + formGroup = form.find(GlFormGroup); + }); + + it('uses label text', () => { + expect(formGroup.attributes().label).toBe('Full dashboard URL'); + }); + + it('uses description text', () => { + expect(formGroup.attributes().description).toBe( + 'Enter the URL of the dashboard you want to link to', + ); + }); + }); + + describe('input field', () => { + let input; + + beforeEach(() => { + input = form.find(GlFormInput); + }); + + it('defaults to externalDashboardPath prop', () => { + expect(input.attributes().value).toBe(externalDashboardPath); + }); + + it('uses a placeholder', () => { + expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards'); + }); + }); + + describe('submit button', () => { + let submit; + + beforeEach(() => { + submit = form.find(GlButton); + }); + + it('renders button label', () => { + expect(submit.text()).toBe('Save Changes'); + }); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js new file mode 100644 index 00000000000..3b6f67457ad --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -0,0 +1,103 @@ +import { GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import SuggestionDiffHeader from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; + +const localVue = createLocalVue(); + +const DEFAULT_PROPS = { + canApply: true, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let wrapper; + + const createComponent = props => { + wrapper = shallowMount(localVue.extend(SuggestionDiffHeader), { + propsData: { + ...DEFAULT_PROPS, + ...props, + }, + localVue, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findApplyButton = () => wrapper.find('.qa-apply-btn'); + const findHeader = () => wrapper.find('.qa-suggestion-diff-header'); + const findHelpButton = () => wrapper.find('.js-help-btn'); + const findLoading = () => wrapper.find(GlLoadingIcon); + + it('renders a suggestion header', () => { + createComponent(); + + const header = findHeader(); + + expect(header.exists()).toBe(true); + expect(header.html().includes('Suggested change')).toBe(true); + }); + + it('renders a help button', () => { + createComponent(); + + expect(findHelpButton().exists()).toBe(true); + }); + + it('renders an apply button', () => { + createComponent(); + + const applyBtn = findApplyButton(); + + expect(applyBtn.exists()).toBe(true); + expect(applyBtn.html().includes('Apply suggestion')).toBe(true); + }); + + it('does not render an apply button if `canApply` is set to false', () => { + createComponent({ canApply: false }); + + expect(findApplyButton().exists()).toBe(false); + }); + + describe('when apply suggestion is clicked', () => { + beforeEach(done => { + createComponent(); + + findApplyButton().vm.$emit('click'); + + wrapper.vm.$nextTick(done); + }); + + it('emits apply', () => { + expect(wrapper.emittedByOrder()).toEqual([{ name: 'apply', args: [expect.any(Function)] }]); + }); + + it('hides apply button', () => { + expect(findApplyButton().exists()).toBe(false); + }); + + it('shows loading', () => { + expect(findLoading().exists()).toBe(true); + expect(wrapper.text()).toContain('Applying suggestion'); + }); + + it('when callback of apply is called, hides loading', done => { + const [callback] = wrapper.emitted().apply[0]; + + callback(); + + wrapper.vm + .$nextTick() + .then(() => { + expect(findApplyButton().exists()).toBe(true); + expect(findLoading().exists()).toBe(false); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 5c28840d3a4..e9bd6050d68 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -20,6 +20,9 @@ const propsData = { emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', environmentsEndpoint: '/root/hello-prometheus/environments/35', currentEnvironmentName: 'production', + customMetricsAvailable: false, + customMetricsPath: '', + validateQueryPath: '', }; export default propsData; @@ -37,6 +40,9 @@ describe('Dashboard', () => { window.gon = { ...window.gon, ee: false, + features: { + grafanaDashboardLink: true, + }, }; mock = new MockAdapter(axios); @@ -160,7 +166,7 @@ describe('Dashboard', () => { }); }); - it('renders the environments dropdown with a single is-active element', done => { + it('renders the environments dropdown with a single active element', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { @@ -175,7 +181,7 @@ describe('Dashboard', () => { setTimeout(() => { const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item.is-active', + '.js-environments-dropdown .dropdown-item[active="true"]', ); expect(dropdownItems.length).toEqual(1); @@ -323,4 +329,63 @@ describe('Dashboard', () => { .catch(done.fail); }); }); + + describe('external dashboard link', () => { + let component; + + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + }); + + afterEach(() => { + component.$destroy(); + }); + + describe('with feature flag enabled', () => { + beforeEach(() => { + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + externalDashboardPath: '/mockPath', + }, + }); + }); + + it('shows the link', done => { + setTimeout(() => { + expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain( + 'View full dashboard', + ); + done(); + }); + }); + }); + + describe('without feature flage enabled', () => { + beforeEach(() => { + window.gon.features.grafanaDashboardLink = false; + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + externalDashboardPath: '', + }, + }); + }); + + it('does not show the link', done => { + setTimeout(() => { + expect(component.$el.querySelector('.js-external-dashboard-link')).toBe(null); + done(); + }); + }); + }); + }); }); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 94ce6d8e222..7a9f32ddcff 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -3,11 +3,12 @@ import $ from 'jquery'; import _ from 'underscore'; import { TEST_HOST } from 'spec/test_constants'; import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; -import * as actions from '~/notes/stores/actions'; +import actionsModule, * as actions from '~/notes/stores/actions'; import * as mutationTypes from '~/notes/stores/mutation_types'; import * as notesConstants from '~/notes/constants'; import createStore from '~/notes/stores'; import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub'; +import service from '~/notes/services/notes_service'; import testAction from '../../helpers/vuex_action_helper'; import { resetStore } from '../helpers'; import { @@ -18,11 +19,21 @@ import { individualNote, } from '../mock_data'; +const TEST_ERROR_MESSAGE = 'Test error message'; + describe('Actions Notes Store', () => { + let commit; + let dispatch; + let state; let store; + let flashSpy; beforeEach(() => { store = createStore(); + commit = jasmine.createSpy('commit'); + dispatch = jasmine.createSpy('dispatch'); + state = {}; + flashSpy = spyOnDependency(actionsModule, 'Flash'); }); afterEach(() => { @@ -604,21 +615,6 @@ describe('Actions Notes Store', () => { }); describe('updateOrCreateNotes', () => { - let commit; - let dispatch; - let state; - - beforeEach(() => { - commit = jasmine.createSpy('commit'); - dispatch = jasmine.createSpy('dispatch'); - state = {}; - }); - - afterEach(() => { - commit.calls.reset(); - dispatch.calls.reset(); - }); - it('Updates existing note', () => { const note = { id: 1234 }; const getters = { notesById: { 1234: note } }; @@ -751,4 +747,151 @@ describe('Actions Notes Store', () => { ); }); }); + + describe('resolveDiscussion', () => { + let getters; + let discussionId; + + beforeEach(() => { + discussionId = discussionMock.id; + state.discussions = [discussionMock]; + getters = { + isDiscussionResolved: () => false, + }; + }); + + it('when unresolved, dispatches action', done => { + testAction( + actions.resolveDiscussion, + { discussionId }, + { ...state, ...getters }, + [], + [ + { + type: 'toggleResolveNote', + payload: { + endpoint: discussionMock.resolve_path, + isResolved: false, + discussion: true, + }, + }, + ], + done, + ); + }); + + it('when resolved, does nothing', done => { + getters.isDiscussionResolved = id => id === discussionId; + + testAction( + actions.resolveDiscussion, + { discussionId }, + { ...state, ...getters }, + [], + [], + done, + ); + }); + }); + + describe('saveNote', () => { + const payload = { endpoint: TEST_HOST, data: { 'note[note]': 'some text' } }; + + describe('if response contains errors', () => { + const res = { errors: { something: ['went wrong'] } }; + + it('throws an error', done => { + actions + .saveNote( + { + commit() {}, + dispatch: () => Promise.resolve(res), + }, + payload, + ) + .then(() => done.fail('Expected error to be thrown!')) + .catch(error => { + expect(error.message).toBe('Failed to save comment!'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('if response contains no errors', () => { + const res = { valid: true }; + + it('returns the response', done => { + actions + .saveNote( + { + commit() {}, + dispatch: () => Promise.resolve(res), + }, + payload, + ) + .then(data => { + expect(data).toBe(res); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('submitSuggestion', () => { + const discussionId = 'discussion-id'; + const noteId = 'note-id'; + const suggestionId = 'suggestion-id'; + let flashContainer; + + beforeEach(() => { + spyOn(service, 'applySuggestion'); + dispatch.and.returnValue(Promise.resolve()); + service.applySuggestion.and.returnValue(Promise.resolve()); + flashContainer = {}; + }); + + const testSubmitSuggestion = (done, expectFn) => { + actions + .submitSuggestion( + { commit, dispatch }, + { discussionId, noteId, suggestionId, flashContainer }, + ) + .then(expectFn) + .then(done) + .catch(done.fail); + }; + + it('when service success, commits and resolves discussion', done => { + testSubmitSuggestion(done, () => { + expect(commit.calls.allArgs()).toEqual([ + [mutationTypes.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }], + ]); + + expect(dispatch.calls.allArgs()).toEqual([['resolveDiscussion', { discussionId }]]); + expect(flashSpy).not.toHaveBeenCalled(); + }); + }); + + it('when service fails, flashes error message', done => { + const response = { response: { data: { message: TEST_ERROR_MESSAGE } } }; + + service.applySuggestion.and.returnValue(Promise.reject(response)); + + testSubmitSuggestion(done, () => { + expect(commit).not.toHaveBeenCalled(); + expect(dispatch).not.toHaveBeenCalled(); + expect(flashSpy).toHaveBeenCalledWith(`${TEST_ERROR_MESSAGE}.`, 'alert', flashContainer); + }); + }); + + it('when resolve discussion fails, fail gracefully', done => { + dispatch.and.returnValue(Promise.reject()); + + testSubmitSuggestion(done, () => { + expect(flashSpy).not.toHaveBeenCalled(); + }); + }); + }); }); diff --git a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js index a89952ee435..5f4dba5ecb9 100644 --- a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js +++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js @@ -3,6 +3,7 @@ import GLDropdown from '~/gl_dropdown'; // eslint-disable-line no-unused-vars import TimezoneDropdown, { formatUtcOffset, formatTimezone, + findTimezoneByIdentifier, } from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown'; describe('Timezone Dropdown', function() { @@ -12,6 +13,7 @@ describe('Timezone Dropdown', function() { let $dropdownEl = null; let $wrapper = null; const tzListSel = '.dropdown-content ul li a.is-active'; + const tzDropdownToggleText = '.dropdown-toggle-text'; describe('Initialize', () => { describe('with dropdown already loaded', () => { @@ -94,6 +96,36 @@ describe('Timezone Dropdown', function() { expect(onSelectTimezone).toHaveBeenCalled(); }); + + it('will correctly set the dropdown label if a timezone identifier is set on the inputEl', () => { + $inputEl.val('America/St_Johns'); + + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + displayFormat: selectedItem => formatTimezone(selectedItem), + }); + + expect($wrapper.find(tzDropdownToggleText).html()).toEqual('[UTC - 2.5] Newfoundland'); + }); + + it('will call a provided `displayFormat` handler to format the dropdown value', () => { + const displayFormat = jasmine.createSpy('displayFormat'); + // eslint-disable-next-line no-new + new TimezoneDropdown({ + $inputEl, + $dropdownEl, + displayFormat, + }); + + $wrapper + .find(tzListSel) + .first() + .trigger('click'); + + expect(displayFormat).toHaveBeenCalled(); + }); }); }); @@ -164,4 +196,49 @@ describe('Timezone Dropdown', function() { ).toEqual('[UTC 0] Accra'); }); }); + + describe('findTimezoneByIdentifier', () => { + const tzList = [ + { + identifier: 'Asia/Tokyo', + name: 'Sapporo', + offset: 32400, + }, + { + identifier: 'Asia/Hong_Kong', + name: 'Hong Kong', + offset: 28800, + }, + { + identifier: 'Asia/Dhaka', + name: 'Dhaka', + offset: 21600, + }, + ]; + + const identifier = 'Asia/Dhaka'; + it('returns the correct object if the identifier exists', () => { + const res = findTimezoneByIdentifier(tzList, identifier); + + expect(res).toBeTruthy(); + expect(res).toBe(tzList[2]); + }); + + it('returns null if it doesnt find the identifier', () => { + const res = findTimezoneByIdentifier(tzList, 'Australia/Melbourne'); + + expect(res).toBeNull(); + }); + + it('returns null if there is no identifier given', () => { + expect(findTimezoneByIdentifier(tzList)).toBeNull(); + expect(findTimezoneByIdentifier(tzList, '')).toBeNull(); + }); + + it('returns null if there is an empty or invalid array given', () => { + expect(findTimezoneByIdentifier([], identifier)).toBeNull(); + expect(findTimezoneByIdentifier(null, identifier)).toBeNull(); + expect(findTimezoneByIdentifier(undefined, identifier)).toBeNull(); + }); + }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index de213210cfc..8ac6e6a7b44 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -78,6 +78,19 @@ describe('MRWidgetPipeline', () => { ); }); + it('should render CI error when no pipeline is provided', () => { + vm = mountComponent(Component, { + pipeline: {}, + hasCi: true, + ciStatus: 'success', + troubleshootingDocsPath: 'help', + }); + + expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( + 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.', + ); + }); + describe('with a pipeline', () => { beforeEach(() => { vm = mountComponent(Component, { diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js deleted file mode 100644 index 12ee804f668..00000000000 --- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import Vue from 'vue'; -import SuggestionDiffHeaderComponent from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; - -const MOCK_DATA = { - canApply: true, - isApplied: false, - helpPagePath: 'path_to_docs', -}; - -describe('Suggestion Diff component', () => { - let vm; - - function createComponent(propsData) { - const Component = Vue.extend(SuggestionDiffHeaderComponent); - - return new Component({ - propsData, - }).$mount(); - } - - beforeEach(done => { - vm = createComponent(MOCK_DATA); - Vue.nextTick(done); - }); - - describe('init', () => { - it('renders a suggestion header', () => { - const header = vm.$el.querySelector('.qa-suggestion-diff-header'); - - expect(header).not.toBeNull(); - expect(header.innerHTML.includes('Suggested change')).toBe(true); - }); - - it('renders a help button', () => { - const helpBtn = vm.$el.querySelector('.js-help-btn'); - - expect(helpBtn).not.toBeNull(); - }); - - it('renders an apply button', () => { - const applyBtn = vm.$el.querySelector('.qa-apply-btn'); - - expect(applyBtn).not.toBeNull(); - expect(applyBtn.innerHTML.includes('Apply suggestion')).toBe(true); - }); - - it('does not render an apply button if `canApply` is set to false', () => { - const props = Object.assign(MOCK_DATA, { canApply: false }); - - vm = createComponent(props); - - expect(vm.$el.querySelector('.qa-apply-btn')).toBeNull(); - }); - }); - - describe('applySuggestion', () => { - it('emits when the apply button is clicked', () => { - const props = Object.assign(MOCK_DATA, { canApply: true }); - - vm = createComponent(props); - spyOn(vm, '$emit'); - vm.applySuggestion(); - - expect(vm.$emit).toHaveBeenCalled(); - }); - - it('does not emit when the canApply is set to false', () => { - spyOn(vm, '$emit'); - vm.canApply = false; - vm.applySuggestion(); - - expect(vm.$emit).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb index 66af7f81535..99fe8795d91 100644 --- a/spec/lib/api/helpers/related_resources_helpers_spec.rb +++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb @@ -5,6 +5,40 @@ describe API::Helpers::RelatedResourcesHelpers do Class.new.include(described_class).new end + describe '#expose_path' do + let(:path) { '/api/v4/awesome_endpoint' } + + context 'empty relative URL root' do + before do + stub_config_setting(relative_url_root: '') + end + + it 'returns the existing path' do + expect(helpers.expose_path(path)).to eq(path) + end + end + + context 'slash relative URL root' do + before do + stub_config_setting(relative_url_root: '/') + end + + it 'returns the existing path' do + expect(helpers.expose_path(path)).to eq(path) + end + end + + context 'with relative URL root' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it 'returns the existing path' do + expect(helpers.expose_path(path)).to eq("/gitlab/root" + path) + end + end + end + describe '#expose_url' do let(:path) { '/api/v4/awesome_endpoint' } subject(:url) { helpers.expose_url(path) } diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb index 7972ff253fe..aaf8c9fa2a0 100644 --- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb @@ -10,17 +10,20 @@ describe Gitlab::Metrics::Samplers::RubySampler do describe '#sample' do it 'samples various statistics' do - expect(Gitlab::Metrics::System).to receive(:memory_usage) + expect(Gitlab::Metrics::System).to receive(:cpu_time) expect(Gitlab::Metrics::System).to receive(:file_descriptor_count) + expect(Gitlab::Metrics::System).to receive(:memory_usage) + expect(Gitlab::Metrics::System).to receive(:process_start_time) + expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors) expect(sampler).to receive(:sample_gc) sampler.sample end - it 'adds a metric containing the memory usage' do + it 'adds a metric containing the process resident memory bytes' do expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000) - expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000) + expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000) sampler.sample end @@ -34,6 +37,27 @@ describe Gitlab::Metrics::Samplers::RubySampler do sampler.sample end + it 'adds a metric containing the process total cpu time' do + expect(Gitlab::Metrics::System).to receive(:cpu_time).and_return(0.51) + expect(sampler.metrics[:process_cpu_seconds_total]).to receive(:set).with({}, 0.51) + + sampler.sample + end + + it 'adds a metric containing the process start time' do + expect(Gitlab::Metrics::System).to receive(:process_start_time).and_return(12345) + expect(sampler.metrics[:process_start_time_seconds]).to receive(:set).with({}, 12345) + + sampler.sample + end + + it 'adds a metric containing the process max file descriptors' do + expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors).and_return(1024) + expect(sampler.metrics[:process_max_fds]).to receive(:set).with({}, 1024) + + sampler.sample + end + it 'clears any GC profiles' do expect(GC::Profiler).to receive(:clear) diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb index 4b03f3c2532..090e456644f 100644 --- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb @@ -39,8 +39,8 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do it 'updates metrics type unix and with addr' do labels = { socket_type: 'unix', socket_address: socket_address } - expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') - expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') + expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active') + expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued') subject.sample end @@ -50,7 +50,6 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do context 'unicorn listens on tcp sockets' do let(:tcp_socket_address) { '0.0.0.0:8080' } let(:tcp_sockets) { [tcp_socket_address] } - before do allow(unicorn).to receive(:listener_names).and_return(tcp_sockets) end @@ -71,13 +70,29 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do it 'updates metrics type unix and with addr' do labels = { socket_type: 'tcp', socket_address: tcp_socket_address } - expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') - expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') + expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active') + expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued') subject.sample end end end + + context 'additional metrics' do + let(:unicorn_workers) { 2 } + + before do + allow(unicorn).to receive(:listener_names).and_return([""]) + allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14) + allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers) + end + + it "sets additional metrics" do + expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers) + + subject.sample + end + end end describe '#start' do diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index 14afcdf5daa..b0603d96eb2 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -13,6 +13,18 @@ describe Gitlab::Metrics::System do expect(described_class.file_descriptor_count).to be > 0 end end + + describe '.max_open_file_descriptors' do + it 'returns the max allowed open file descriptors' do + expect(described_class.max_open_file_descriptors).to be > 0 + end + end + + describe '.process_start_time' do + it 'returns the process start time' do + expect(described_class.process_start_time).to be > 0 + end + end else describe '.memory_usage' do it 'returns 0.0' do @@ -25,6 +37,18 @@ describe Gitlab::Metrics::System do expect(described_class.file_descriptor_count).to eq(0) end end + + describe '.max_open_file_descriptors' do + it 'returns 0' do + expect(described_class.max_open_file_descriptors).to eq(0) + end + end + + describe 'process_start_time' do + it 'returns 0' do + expect(described_class.process_start_time).to eq(0) + end + end end describe '.cpu_time' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index bb0257e7456..2a17bd6002e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3164,61 +3164,105 @@ describe Project do end describe '.with_feature_available_for_user' do - let!(:user) { create(:user) } - let!(:feature) { MergeRequest } - let!(:project) { create(:project, :public, :merge_requests_enabled) } + let(:user) { create(:user) } + let(:feature) { MergeRequest } subject { described_class.with_feature_available_for_user(feature, user) } - context 'when user has access to project' do - subject { described_class.with_feature_available_for_user(feature, user) } + shared_examples 'feature disabled' do + let(:project) { create(:project, :public, :merge_requests_disabled) } + + it 'does not return projects with the project feature disabled' do + is_expected.not_to include(project) + end + end + + shared_examples 'feature public' do + let(:project) { create(:project, :public, :merge_requests_public) } + + it 'returns projects with the project feature public' do + is_expected.to include(project) + end + end + + shared_examples 'feature enabled' do + let(:project) { create(:project, :public, :merge_requests_enabled) } + + it 'returns projects with the project feature enabled' do + is_expected.to include(project) + end + end + + shared_examples 'feature access level is nil' do + let(:project) { create(:project, :public) } + + it 'returns projects with the project feature access level nil' do + project.project_feature.update(merge_requests_access_level: nil) + + is_expected.to include(project) + end + end + context 'with user' do before do project.add_guest(user) end - context 'when public project' do - context 'when feature is public' do - it 'returns project' do - is_expected.to include(project) + it_behaves_like 'feature disabled' + it_behaves_like 'feature public' + it_behaves_like 'feature enabled' + it_behaves_like 'feature access level is nil' + + context 'when feature is private' do + let(:project) { create(:project, :public, :merge_requests_private) } + + context 'when user does not has access to the feature' do + it 'does not return projects with the project feature private' do + is_expected.not_to include(project) end end - context 'when feature is private' do - let!(:project) { create(:project, :public, :merge_requests_private) } - - it 'returns project when user has access to the feature' do - project.add_maintainer(user) + context 'when user has access to the feature' do + it 'returns projects with the project feature private' do + project.add_reporter(user) is_expected.to include(project) end - - it 'does not return project when user does not have the minimum access level required' do - is_expected.not_to include(project) - end end end + end - context 'when private project' do - let!(:project) { create(:project) } + context 'user is an admin' do + let(:user) { create(:user, :admin) } - it 'returns project when user has access to the feature' do - project.add_maintainer(user) + it_behaves_like 'feature disabled' + it_behaves_like 'feature public' + it_behaves_like 'feature enabled' + it_behaves_like 'feature access level is nil' - is_expected.to include(project) - end + context 'when feature is private' do + let(:project) { create(:project, :public, :merge_requests_private) } - it 'does not return project when user does not have the minimum access level required' do - is_expected.not_to include(project) + it 'returns projects with the project feature private' do + is_expected.to include(project) end end end - context 'when user does not have access to project' do - let!(:project) { create(:project) } + context 'without user' do + let(:user) { nil } - it 'does not return project when user cant access project' do - is_expected.not_to include(project) + it_behaves_like 'feature disabled' + it_behaves_like 'feature public' + it_behaves_like 'feature enabled' + it_behaves_like 'feature access level is nil' + + context 'when feature is private' do + let(:project) { create(:project, :public, :merge_requests_private) } + + it 'does not return projects with the project feature private' do + is_expected.not_to include(project) + end end end end diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index f743dfed31f..e14b19db915 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -373,6 +373,22 @@ describe RemoteMirror, :mailer do end end + describe '#disabled?' do + subject { remote_mirror.disabled? } + + context 'when disabled' do + let(:remote_mirror) { build(:remote_mirror, enabled: false) } + + it { is_expected.to be_truthy } + end + + context 'when enabled' do + let(:remote_mirror) { build(:remote_mirror, enabled: true) } + + it { is_expected.to be_falsy } + end + end + def create_mirror(params) project = FactoryBot.create(:project, :repository) project.remote_mirrors.create!(params) diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb index b2ef17a81d4..e09c91e874a 100644 --- a/spec/models/user_preference_spec.rb +++ b/spec/models/user_preference_spec.rb @@ -73,4 +73,10 @@ describe UserPreference do it_behaves_like 'a sort_by preference' end end + + describe '#timezone' do + it 'returns server time as default' do + expect(user_preference.timezone).to eq(Time.zone.tzinfo.name) + end + end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5c94a87529b..007f3517e64 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1495,6 +1495,33 @@ describe API::MergeRequests do expect(json_response['merge_when_pipeline_succeeds']).to eq(true) end + context 'when the MR requires pipeline success' do + it 'returns 405 if the pipeline is missing' do + allow_any_instance_of(MergeRequest) + .to receive(:merge_when_pipeline_succeeds).and_return(true) + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(nil) + + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user) + + expect(response).to have_gitlab_http_status(405) + expect(json_response['message']).to eq('Not allowed: pipeline does not exist') + end + end + + context 'when the request requires pipeline success' do + it 'returns 405 if the pipeline is missing' do + allow_any_instance_of(MergeRequest) + .to receive(:merge_when_pipeline_succeeds).and_return(true) + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(nil) + + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), + params: { merge_when_pipeline_succeeds: true } + + expect(response).to have_gitlab_http_status(405) + expect(json_response['message']).to eq('Not allowed: pipeline does not exist') + end + end + it "returns 404 for an invalid merge request IID" do put api("/projects/#{project.id}/merge_requests/12345/merge", user) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 577f61ae8d0..16d306f39cd 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -504,8 +504,9 @@ describe API::Projects do project4.add_reporter(user2) end - it 'returns an array of groups the user has at least developer access' do + it 'returns an array of projects the user has at least developer access' do get api('/projects', user2), params: { min_access_level: 30 } + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index e04c05418b0..9384287f98a 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -13,6 +13,15 @@ describe Users::UpdateService do expect(user.name).to eq('New Name') end + it 'updates time preferences' do + result = update_user(user, timezone: 'Europe/Warsaw', time_display_relative: true, time_format_in_24h: false) + + expect(result).to eq(status: :success) + expect(user.reload.timezone).to eq('Europe/Warsaw') + expect(user.time_display_relative).to eq(true) + expect(user.time_format_in_24h).to eq(false) + end + it 'returns an error result when record cannot be updated' do result = {} expect do diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 18a7a392c12..875a9a76e12 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -17,6 +17,8 @@ JS_CONSOLE_FILTER = Regexp.union([ "Download the Vue Devtools extension" ]) +CAPYBARA_WINDOW_SIZE = [1366, 768].freeze + Capybara.register_driver :chrome do |app| capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( # This enables access to logs with `page.driver.manage.get_log(:browser)` @@ -29,7 +31,7 @@ Capybara.register_driver :chrome do |app| ) options = Selenium::WebDriver::Chrome::Options.new - options.add_argument("window-size=1240,1400") + options.add_argument("window-size=#{CAPYBARA_WINDOW_SIZE.join(',')}") # Chrome won't work properly in a Docker container in sandbox mode options.add_argument("no-sandbox") @@ -78,8 +80,11 @@ RSpec.configure do |config| protocol: 'http') # reset window size between tests - unless session.current_window.size == [1240, 1400] - session.current_window.resize_to(1240, 1400) rescue nil + unless session.current_window.size == CAPYBARA_WINDOW_SIZE + begin + session.current_window.resize_to(*CAPYBARA_WINDOW_SIZE) + rescue # ? + end end end diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 89517fde6e2..38f30a14409 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -23,8 +23,18 @@ module Spec def preview_note(text) page.within('.js-main-target-form') do - fill_in('note[note]', with: text) + filled_text = fill_in('note[note]', with: text) + + begin + # Dismiss quick action prompt if it appears + filled_text.parent.send_keys(:escape) + rescue Selenium::WebDriver::Error::ElementNotInteractableError + # It's fine if we can't escape when there's no prompt. + end + click_on('Preview') + + yield if block_given? end end end diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb index 9dc1f1de436..4230d315d9b 100644 --- a/spec/support/helpers/mobile_helpers.rb +++ b/spec/support/helpers/mobile_helpers.rb @@ -8,7 +8,7 @@ module MobileHelpers end def restore_window_size - resize_window(1366, 768) + resize_window(*CAPYBARA_WINDOW_SIZE) end def resize_window(width, height) diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb index e0d0b790a0e..a79a61bc708 100644 --- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true shared_examples 'close quick action' do |issuable_type| + include Spec::Support::Helpers::Features::NotesHelpers + before do project.add_maintainer(maintainer) gitlab_sign_in(maintainer) @@ -76,10 +78,7 @@ shared_examples 'close quick action' do |issuable_type| it 'explains close quick action' do visit public_send("project_#{issuable_type}_path", project, issuable) - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "this is done, close\n/close" - click_on 'Preview' - + preview_note("this is done, close\n/close") do expect(page).not_to have_content '/close' expect(page).to have_content 'this is done, close' expect(page).to have_content "Closes this #{issuable_type.to_s.humanize.downcase}." diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb index 825c1cabc14..2dea48e3a88 100644 --- a/spec/uploaders/import_export_uploader_spec.rb +++ b/spec/uploaders/import_export_uploader_spec.rb @@ -3,9 +3,18 @@ require 'spec_helper' describe ImportExportUploader do let(:model) { build_stubbed(:import_export_upload) } let(:upload) { create(:upload, model: model) } + let(:import_export_upload) { ImportExportUpload.new } subject { described_class.new(model, :import_file) } + context 'local store' do + describe '#move_to_store' do + it 'returns true' do + expect(subject.move_to_store).to be true + end + end + end + context "object_store is REMOTE" do before do stub_uploads_object_storage @@ -16,5 +25,28 @@ describe ImportExportUploader do it_behaves_like 'builds correct paths', store_dir: %r[import_export_upload/import_file/], upload_path: %r[import_export_upload/import_file/] + + describe '#move_to_store' do + it 'returns false' do + expect(subject.move_to_store).to be false + end + end + + describe 'with an export file directly uploaded' do + let(:tempfile) { Tempfile.new(['test', '.gz']) } + + before do + stub_uploads_object_storage(described_class, direct_upload: true) + import_export_upload.export_file = tempfile + end + + it 'cleans up cached file' do + cache_dir = File.join(import_export_upload.export_file.cache_path(nil), '*') + + import_export_upload.save! + + expect(Dir[cache_dir]).to be_empty + end + end end end |