diff options
Diffstat (limited to 'spec')
54 files changed, 1051 insertions, 833 deletions
diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb index 25429cdd149..56a6efab8ed 100644 --- a/spec/controllers/concerns/confirm_email_warning_spec.rb +++ b/spec/controllers/concerns/confirm_email_warning_spec.rb @@ -10,7 +10,7 @@ describe ConfirmEmailWarning do controller(ApplicationController) do # `described_class` is not available in this context - include ConfirmEmailWarning # rubocop:disable RSpec/DescribedClass + include ConfirmEmailWarning def index head :ok diff --git a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb index 7a56f7203b0..e47f1650b1f 100644 --- a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb +++ b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb @@ -22,7 +22,7 @@ describe ControllerWithCrossProjectAccessCheck do describe '#requires_cross_project_access' do controller(ApplicationController) do # `described_class` is not available in this context - include ControllerWithCrossProjectAccessCheck # rubocop:disable RSpec/DescribedClass + include ControllerWithCrossProjectAccessCheck requires_cross_project_access :index, show: false, unless: -> { unless_condition }, @@ -81,7 +81,7 @@ describe ControllerWithCrossProjectAccessCheck do describe '#skip_cross_project_access_check' do controller(ApplicationController) do # `described_class` is not available in this context - include ControllerWithCrossProjectAccessCheck # rubocop:disable RSpec/DescribedClass + include ControllerWithCrossProjectAccessCheck requires_cross_project_access diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb index 835c3d9b3af..543f0170be0 100644 --- a/spec/controllers/concerns/group_tree_spec.rb +++ b/spec/controllers/concerns/group_tree_spec.rb @@ -8,7 +8,7 @@ describe GroupTree do controller(ApplicationController) do # `described_class` is not available in this context - include GroupTree # rubocop:disable RSpec/DescribedClass + include GroupTree def index render_group_tree GroupsFinder.new(current_user).execute diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb index 823b9a50434..584448e68f9 100644 --- a/spec/controllers/concerns/lfs_request_spec.rb +++ b/spec/controllers/concerns/lfs_request_spec.rb @@ -7,7 +7,7 @@ describe LfsRequest do controller(Projects::GitHttpClientController) do # `described_class` is not available in this context - include LfsRequest # rubocop:disable RSpec/DescribedClass + include LfsRequest def show storage_project diff --git a/spec/controllers/concerns/metrics_dashboard_spec.rb b/spec/controllers/concerns/metrics_dashboard_spec.rb index ff2b6fbb8ec..389d264bed3 100644 --- a/spec/controllers/concerns/metrics_dashboard_spec.rb +++ b/spec/controllers/concerns/metrics_dashboard_spec.rb @@ -16,7 +16,7 @@ describe MetricsDashboard do end controller(::ApplicationController) do - include MetricsDashboard # rubocop:disable RSpec/DescribedClass + include MetricsDashboard end let(:json_response) do diff --git a/spec/controllers/concerns/renders_commits_spec.rb b/spec/controllers/concerns/renders_commits_spec.rb index 79350847383..c43ceb6b795 100644 --- a/spec/controllers/concerns/renders_commits_spec.rb +++ b/spec/controllers/concerns/renders_commits_spec.rb @@ -9,7 +9,7 @@ describe RendersCommits do controller(ApplicationController) do # `described_class` is not available in this context - include RendersCommits # rubocop:disable RSpec/DescribedClass + include RendersCommits def index @merge_request = MergeRequest.find(params[:id]) diff --git a/spec/controllers/concerns/routable_actions_spec.rb b/spec/controllers/concerns/routable_actions_spec.rb index 59d48c68b9c..a11f4d2a154 100644 --- a/spec/controllers/concerns/routable_actions_spec.rb +++ b/spec/controllers/concerns/routable_actions_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe RoutableActions do controller(::ApplicationController) do - include RoutableActions # rubocop:disable RSpec/DescribedClass + include RoutableActions before_action :routable diff --git a/spec/controllers/concerns/sourcegraph_gon_spec.rb b/spec/controllers/concerns/sourcegraph_gon_spec.rb index 4fb7e37d148..d9273987871 100644 --- a/spec/controllers/concerns/sourcegraph_gon_spec.rb +++ b/spec/controllers/concerns/sourcegraph_gon_spec.rb @@ -17,7 +17,7 @@ describe SourcegraphGon do let(:project) { internal_project } controller(ApplicationController) do - include SourcegraphGon # rubocop:disable RSpec/DescribedClass + include SourcegraphGon def index head :ok diff --git a/spec/controllers/concerns/static_object_external_storage_spec.rb b/spec/controllers/concerns/static_object_external_storage_spec.rb index 3a0219ddaa1..ddd1a95427e 100644 --- a/spec/controllers/concerns/static_object_external_storage_spec.rb +++ b/spec/controllers/concerns/static_object_external_storage_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe StaticObjectExternalStorage do controller(Projects::ApplicationController) do - include StaticObjectExternalStorage # rubocop:disable RSpec/DescribedClass + include StaticObjectExternalStorage before_action :redirect_to_external_storage, if: :static_objects_external_storage_enabled? diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 757d8704a6a..ac39ac626c7 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -591,7 +591,7 @@ describe Projects::BranchesController do params: { namespace_id: project.namespace, project_id: project, - names: ['fix', 'add-pdf-file', 'branch-merged'] + names: %w[fix add-pdf-file branch-merged] } expect(response).to have_gitlab_http_status(200) @@ -639,7 +639,7 @@ describe Projects::BranchesController do params: { namespace_id: project.namespace, project_id: project, - names: ['fix', 'add-pdf-file', 'branch-merged'] + names: %w[fix add-pdf-file branch-merged] } expect(response).to have_gitlab_http_status(200) diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb index f80bbf0d78f..c07619465bf 100644 --- a/spec/controllers/projects/pages_controller_spec.rb +++ b/spec/controllers/projects/pages_controller_spec.rb @@ -115,5 +115,16 @@ describe Projects::PagesController do patch :update, params: request_params end + + context 'when update_service returns an error message' do + let(:update_service) { double(execute: { status: :error, message: 'some error happened' }) } + + it 'adds an error message' do + patch :update, params: request_params + + expect(response).to redirect_to(project_pages_path(project)) + expect(flash[:alert]).to eq('some error happened') + end + end end end diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index 10672bbec68..657513431e5 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -229,6 +229,16 @@ describe "User browses files" do expect(page).to have_content("*.rb") .and have_content("Dmitriy Zaporozhets") .and have_content("Initial commit") + .and have_content("Ignore DS files") + + previous_commit_anchor = "//a[@title='Ignore DS files']/parent::span/following-sibling::span/a" + find(:xpath, previous_commit_anchor).click + + expect(page).to have_content("*.rb") + .and have_content("Dmitriy Zaporozhets") + .and have_content("Initial commit") + + expect(page).not_to have_content("Ignore DS files") end end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 3c4b5b2c4ca..afd1178f7f2 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -322,7 +322,7 @@ shared_examples 'pages settings editing' do before do allow(Projects::UpdateService).to receive(:new).and_return(service) - allow(service).to receive(:execute).and_return(status: :error) + allow(service).to receive(:execute).and_return(status: :error, message: 'Some error has occured') end it 'tries to change the setting' do @@ -332,7 +332,7 @@ shared_examples 'pages settings editing' do click_button 'Save' - expect(page).to have_text('Something went wrong on our end') + expect(page).to have_text('Some error has occured') end end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index ad65e04473c..94af023e804 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -50,7 +50,7 @@ describe 'Projects > Snippets > Create Snippet', :js do wait_for_requests link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/#{Regexp.escape(project.full_path) }/uploads/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) end it 'creates a snippet when all required fields are filled in after validation failing' do @@ -72,7 +72,7 @@ describe 'Projects > Snippets > Create Snippet', :js do expect(page).to have_selector('strong') end link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/#{Regexp.escape(project.full_path) }/uploads/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) end end diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index 70b5da0cc3c..5f75ff8c6ff 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -66,7 +66,7 @@ describe BranchesFinder do end it 'filters branches by provided names' do - branches_finder = described_class.new(repository, { names: ['fix', 'csv', 'lfs', 'does-not-exist'] }) + branches_finder = described_class.new(repository, { names: %w[fix csv lfs does-not-exist] }) result = branches_finder.execute diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index a837e7af251..a35c3a954e7 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -219,7 +219,7 @@ describe TodosFinder do end it "sorts by priority" do - project_2 = create(:project) + project_2 = create(:project) label_1 = create(:label, title: 'label_1', project: project, priority: 1) label_2 = create(:label, title: 'label_2', project: project, priority: 2) diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js new file mode 100644 index 00000000000..8a10857d0ff --- /dev/null +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -0,0 +1,573 @@ +import { shallowMount, createLocalVue, mount } from '@vue/test-utils'; +import { GlDropdownItem, GlButton, GlToast } from '@gitlab/ui'; +import VueDraggable from 'vuedraggable'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import statusCodes from '~/lib/utils/http_status'; +import { metricStates } from '~/monitoring/constants'; +import Dashboard from '~/monitoring/components/dashboard.vue'; +import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue'; +import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; +import { createStore } from '~/monitoring/stores'; +import * as types from '~/monitoring/stores/mutation_types'; +import * as monitoringUtils from '~/monitoring/utils'; +import { setupComponentStore, propsData } from '../init_utils'; +import { + metricsGroupsAPIResponse, + mockedQueryResultPayload, + mockApiEndpoint, + environmentData, + dashboardGitResponse, +} from '../mock_data'; + +const localVue = createLocalVue(); +const expectedPanelCount = 2; + +describe('Dashboard', () => { + let DashboardComponent; + let store; + let wrapper; + let mock; + + const createShallowWrapper = (props = {}, options = {}) => { + wrapper = shallowMount(localVue.extend(DashboardComponent), { + localVue, + sync: false, + propsData: { ...propsData, ...props }, + store, + ...options, + }); + }; + + const createMountedWrapper = (props = {}, options = {}) => { + wrapper = mount(localVue.extend(DashboardComponent), { + localVue, + sync: false, + propsData: { ...propsData, ...props }, + store, + ...options, + }); + }; + + beforeEach(() => { + store = createStore(); + DashboardComponent = localVue.extend(Dashboard); + mock = new MockAdapter(axios); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + mock.restore(); + }); + + describe('no metrics are available yet', () => { + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse); + + createShallowWrapper({}, { attachToDocument: true }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('shows the environment selector', () => { + expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); + }); + }); + + describe('no data found', () => { + beforeEach(done => { + mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse); + + createShallowWrapper({}, { attachToDocument: true }); + + wrapper.vm.$nextTick(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('shows the environment selector dropdown', () => { + expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); + }); + }); + + describe('request information to the server', () => { + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + }); + + it('shows up a loading state', done => { + createShallowWrapper({ hasMetrics: true }, { attachToDocument: true }); + + wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.vm.emptyState).toEqual('loading'); + + done(); + }) + .catch(done.fail); + }); + + it('hides the group panels when showPanels is false', done => { + createMountedWrapper( + { hasMetrics: true, showPanels: false }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + setupComponentStore(wrapper); + + wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.vm.showEmptyState).toEqual(false); + expect(wrapper.vm.$el.querySelector('.prometheus-panel')).toEqual(null); + // TODO: The last expectation doesn't belong here, it belongs in a `group_group_spec.js` file + // Issue: https://gitlab.com/gitlab-org/gitlab/issues/118780 + // expect(wrapper.vm.$el.querySelector('.prometheus-graph-group')).toBeTruthy(); + + done(); + }) + .catch(done.fail); + }); + + it('fetches the metrics data with proper time window', done => { + const getTimeDiffSpy = jest.spyOn(monitoringUtils, 'getTimeDiff'); + jest.spyOn(store, 'dispatch'); + + createMountedWrapper( + { hasMetrics: true }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + + wrapper.vm + .$nextTick() + .then(() => { + expect(store.dispatch).toHaveBeenCalled(); + expect(getTimeDiffSpy).toHaveBeenCalled(); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('when all requests have been commited by the store', () => { + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse); + + createMountedWrapper( + { hasMetrics: true }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + setupComponentStore(wrapper); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the environments dropdown with a number of environments', done => { + wrapper.vm + .$nextTick() + .then(() => { + const environmentDropdownItems = wrapper + .find('.js-environments-dropdown') + .findAll(GlDropdownItem); + + expect(wrapper.vm.environments.length).toEqual(environmentData.length); + expect(environmentDropdownItems.length).toEqual(wrapper.vm.environments.length); + + environmentDropdownItems.wrappers.forEach((itemWrapper, index) => { + const anchorEl = itemWrapper.find('a'); + if (anchorEl.exists() && environmentData[index].metrics_path) { + const href = anchorEl.attributes('href'); + expect(href).toBe(environmentData[index].metrics_path); + } + }); + + done(); + }) + .catch(done.fail); + }); + + it('renders the environments dropdown with a single active element', done => { + wrapper.vm + .$nextTick() + .then(() => { + const environmentDropdownItems = wrapper + .find('.js-environments-dropdown') + .findAll(GlDropdownItem); + const activeItem = environmentDropdownItems.wrappers.filter(itemWrapper => + itemWrapper.find('.active').exists(), + ); + + expect(activeItem.length).toBe(1); + done(); + }) + .catch(done.fail); + }); + }); + + it('hides the environments dropdown list when there is no environments', done => { + createMountedWrapper( + { hasMetrics: true }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + metricsGroupsAPIResponse, + ); + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedQueryResultPayload, + ); + + wrapper.vm + .$nextTick() + .then(() => { + const environmentDropdownItems = wrapper + .find('.js-environments-dropdown') + .findAll(GlDropdownItem); + + expect(environmentDropdownItems.length).toEqual(0); + done(); + }) + .catch(done.fail); + }); + + it('renders the datetimepicker dropdown', done => { + createMountedWrapper( + { hasMetrics: true }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + setupComponentStore(wrapper); + + wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.find(DateTimePicker).exists()).toBe(true); + done(); + }) + .catch(done.fail); + }); + + describe('when one of the metrics is missing', () => { + beforeEach(done => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + createShallowWrapper({ hasMetrics: true }, { attachToDocument: true }); + setupComponentStore(wrapper); + + wrapper.vm.$nextTick(done); + }); + + it('shows a group empty area', () => { + const emptyGroup = wrapper.findAll({ ref: 'empty-group' }); + + expect(emptyGroup).toHaveLength(1); + expect(emptyGroup.is(GroupEmptyState)).toBe(true); + }); + + it('group empty area displays a NO_DATA state', () => { + expect( + wrapper + .findAll({ ref: 'empty-group' }) + .at(0) + .props('selectedState'), + ).toEqual(metricStates.NO_DATA); + }); + }); + + describe('drag and drop function', () => { + const findDraggables = () => wrapper.findAll(VueDraggable); + const findEnabledDraggables = () => findDraggables().filter(f => !f.attributes('disabled')); + const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel'); + const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); + + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse); + }); + + beforeEach(done => { + createShallowWrapper({ hasMetrics: true }, { attachToDocument: true }); + + setupComponentStore(wrapper); + + wrapper.vm.$nextTick(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('wraps vuedraggable', () => { + expect(findDraggablePanels().exists()).toBe(true); + expect(findDraggablePanels().length).toEqual(expectedPanelCount); + }); + + it('is disabled by default', () => { + expect(findRearrangeButton().exists()).toBe(false); + expect(findEnabledDraggables().length).toBe(0); + }); + + describe('when rearrange is enabled', () => { + beforeEach(done => { + wrapper.setProps({ rearrangePanelsAvailable: true }); + wrapper.vm.$nextTick(done); + }); + + it('displays rearrange button', () => { + expect(findRearrangeButton().exists()).toBe(true); + }); + + describe('when rearrange button is clicked', () => { + const findFirstDraggableRemoveButton = () => + findDraggablePanels() + .at(0) + .find('.js-draggable-remove'); + + beforeEach(done => { + findRearrangeButton().vm.$emit('click'); + wrapper.vm.$nextTick(done); + }); + + it('it enables draggables', () => { + expect(findRearrangeButton().attributes('pressed')).toBeTruthy(); + expect(findEnabledDraggables()).toEqual(findDraggables()); + }); + + it('metrics can be swapped', done => { + const firstDraggable = findDraggables().at(0); + const mockMetrics = [...metricsGroupsAPIResponse[1].panels]; + + const firstTitle = mockMetrics[0].title; + const secondTitle = mockMetrics[1].title; + + // swap two elements and `input` them + [mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]]; + firstDraggable.vm.$emit('input', mockMetrics); + + wrapper.vm.$nextTick(() => { + const { panels } = wrapper.vm.dashboard.panel_groups[1]; + + expect(panels[1].title).toEqual(firstTitle); + expect(panels[0].title).toEqual(secondTitle); + done(); + }); + }); + + it('shows a remove button, which removes a panel', done => { + expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false); + + expect(findDraggablePanels().length).toEqual(expectedPanelCount); + findFirstDraggableRemoveButton().trigger('click'); + + wrapper.vm.$nextTick(() => { + expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1); + done(); + }); + }); + + it('it disables draggables when clicked again', done => { + findRearrangeButton().vm.$emit('click'); + wrapper.vm.$nextTick(() => { + expect(findRearrangeButton().attributes('pressed')).toBeFalsy(); + expect(findEnabledDraggables().length).toBe(0); + done(); + }); + }); + }); + }); + }); + + describe('cluster health', () => { + beforeEach(done => { + mock.onGet(propsData.metricsEndpoint).reply(statusCodes.OK, JSON.stringify({})); + createShallowWrapper({ hasMetrics: true }); + + // all_dashboards is not defined in health dashboards + wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined); + wrapper.vm.$nextTick(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders correctly', () => { + expect(wrapper.isVueInstance()).toBe(true); + expect(wrapper.exists()).toBe(true); + }); + }); + + describe('dashboard edit link', () => { + const findEditLink = () => wrapper.find('.js-edit-link'); + + beforeEach(done => { + mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse); + + createShallowWrapper({ hasMetrics: true }, { attachToDocument: true }); + + wrapper.vm.$store.commit( + `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, + dashboardGitResponse, + ); + wrapper.vm.$nextTick(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('is not present for the default dashboard', () => { + expect(findEditLink().exists()).toBe(false); + }); + + it('is present for a custom dashboard, and links to its edit_path', done => { + const dashboard = dashboardGitResponse[1]; // non-default dashboard + const currentDashboard = dashboard.path; + + wrapper.setProps({ currentDashboard }); + wrapper.vm + .$nextTick() + .then(() => { + expect(findEditLink().exists()).toBe(true); + expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path); + done(); + }) + .catch(done.fail); + }); + }); + + describe('Dashboard dropdown', () => { + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + createMountedWrapper( + { hasMetrics: true }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + wrapper.vm.$store.commit( + `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, + dashboardGitResponse, + ); + }); + + it('shows the dashboard dropdown', done => { + wrapper.vm + .$nextTick() + .then(() => { + const dashboardDropdown = wrapper.find('.js-dashboards-dropdown'); + + expect(dashboardDropdown.exists()).toBe(true); + done(); + }) + .catch(done.fail); + }); + }); + + describe('external dashboard link', () => { + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + createMountedWrapper( + { + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + externalDashboardUrl: '/mockUrl', + }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + }); + + it('shows the link', done => { + wrapper.vm + .$nextTick() + .then(() => { + const externalDashboardButton = wrapper.find('.js-external-dashboard-link'); + + expect(externalDashboardButton.exists()).toBe(true); + expect(externalDashboardButton.is(GlButton)).toBe(true); + expect(externalDashboardButton.text()).toContain('View full dashboard'); + done(); + }) + .catch(done.fail); + }); + }); + + // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922 + // eslint-disable-next-line jest/no-disabled-tests + describe.skip('link to chart', () => { + const currentDashboard = 'TEST_DASHBOARD'; + localVue.use(GlToast); + const link = () => wrapper.find('.js-chart-link'); + const clipboardText = () => link().element.dataset.clipboardText; + + beforeEach(done => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + createShallowWrapper({ hasMetrics: true, currentDashboard }, { attachToDocument: true }); + + setTimeout(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('adds a copy button to the dropdown', () => { + expect(link().text()).toContain('Generate link to chart'); + }); + + it('contains a link to the dashboard', () => { + expect(clipboardText()).toContain(`dashboard=${currentDashboard}`); + expect(clipboardText()).toContain(`group=`); + expect(clipboardText()).toContain(`title=`); + expect(clipboardText()).toContain(`y_label=`); + }); + + it('undefined parameter is stripped', done => { + wrapper.setProps({ currentDashboard: undefined }); + + wrapper.vm.$nextTick(() => { + expect(clipboardText()).not.toContain(`dashboard=`); + expect(clipboardText()).toContain(`y_label=`); + done(); + }); + }); + + it('null parameter is stripped', done => { + wrapper.setProps({ currentDashboard: null }); + + wrapper.vm.$nextTick(() => { + expect(clipboardText()).not.toContain(`dashboard=`); + expect(clipboardText()).toContain(`y_label=`); + done(); + }); + }); + + it('creates a toast when clicked', () => { + jest.spyOn(wrapper.vm.$toast, 'show').and.stub(); + + link().vm.$emit('click'); + + expect(wrapper.vm.$toast.show).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/dashboard_time_url_spec.js b/spec/frontend/monitoring/components/dashboard_time_url_spec.js new file mode 100644 index 00000000000..8dc450cf131 --- /dev/null +++ b/spec/frontend/monitoring/components/dashboard_time_url_spec.js @@ -0,0 +1,54 @@ +import { mount, createLocalVue } from '@vue/test-utils'; +import createFlash from '~/flash'; +import Dashboard from '~/monitoring/components/dashboard.vue'; +import { createStore } from '~/monitoring/stores'; +import { propsData } from '../init_utils'; + +const localVue = createLocalVue(); + +jest.mock('~/flash'); + +jest.mock('~/lib/utils/url_utility', () => ({ + getParameterValues: jest.fn().mockReturnValue('<script>alert("XSS")</script>'), +})); + +describe('dashboard invalid url parameters', () => { + let store; + let wrapper; + + const createMountedWrapper = (props = {}, options = {}) => { + wrapper = mount(localVue.extend(Dashboard), { + localVue, + sync: false, + propsData: { ...propsData, ...props }, + store, + ...options, + }); + }; + + beforeEach(() => { + store = createStore(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + it('shows an error message if invalid url parameters are passed', done => { + createMountedWrapper( + { hasMetrics: true }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + wrapper.vm + .$nextTick() + .then(() => { + expect(createFlash).toHaveBeenCalled(); + + done(); + }) + .catch(done.fail); + }); +}); diff --git a/spec/frontend/monitoring/components/dashboard_time_window_spec.js b/spec/frontend/monitoring/components/dashboard_time_window_spec.js new file mode 100644 index 00000000000..d49af6f84cb --- /dev/null +++ b/spec/frontend/monitoring/components/dashboard_time_window_spec.js @@ -0,0 +1,75 @@ +import { mount, createLocalVue } from '@vue/test-utils'; +import { GlDropdownItem } from '@gitlab/ui'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import statusCodes from '~/lib/utils/http_status'; +import Dashboard from '~/monitoring/components/dashboard.vue'; +import { createStore } from '~/monitoring/stores'; +import { propsData, setupComponentStore } from '../init_utils'; +import { metricsGroupsAPIResponse, mockApiEndpoint } from '../mock_data'; + +const localVue = createLocalVue(); + +jest.mock('~/lib/utils/url_utility', () => ({ + getParameterValues: jest.fn().mockImplementation(param => { + if (param === 'start') return ['2019-10-01T18:27:47.000Z']; + if (param === 'end') return ['2019-10-01T18:57:47.000Z']; + return []; + }), + mergeUrlParams: jest.fn().mockReturnValue('#'), +})); + +describe('dashboard time window', () => { + let store; + let wrapper; + let mock; + + const createComponentWrapperMounted = (props = {}, options = {}) => { + wrapper = mount(localVue.extend(Dashboard), { + localVue, + sync: false, + propsData: { ...propsData, ...props }, + store, + ...options, + }); + }; + + beforeEach(() => { + store = createStore(); + mock = new MockAdapter(axios); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + mock.restore(); + }); + + it('shows an error message if invalid url parameters are passed', done => { + mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse); + + createComponentWrapperMounted( + { hasMetrics: true }, + { attachToDocument: true, stubs: ['graph-group', 'panel-type'] }, + ); + + setupComponentStore(wrapper); + + wrapper.vm + .$nextTick() + .then(() => { + const timeWindowDropdownItems = wrapper + .find('.js-time-window-dropdown') + .findAll(GlDropdownItem); + const activeItem = timeWindowDropdownItems.wrappers.filter(itemWrapper => + itemWrapper.find('.active').exists(), + ); + + expect(activeItem.length).toBe(1); + + done(); + }) + .catch(done.fail); + }); +}); diff --git a/spec/javascripts/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js index 43ca17c3cbc..43ca17c3cbc 100644 --- a/spec/javascripts/monitoring/components/graph_group_spec.js +++ b/spec/frontend/monitoring/components/graph_group_spec.js diff --git a/spec/frontend/monitoring/init_utils.js b/spec/frontend/monitoring/init_utils.js new file mode 100644 index 00000000000..10db8b902b5 --- /dev/null +++ b/spec/frontend/monitoring/init_utils.js @@ -0,0 +1,56 @@ +import * as types from '~/monitoring/stores/mutation_types'; +import { + metricsGroupsAPIResponse, + mockedEmptyResult, + mockedQueryResultPayload, + mockedQueryResultPayloadCoresTotal, + mockApiEndpoint, + environmentData, +} from './mock_data'; + +export const propsData = { + hasMetrics: false, + documentationPath: '/path/to/docs', + settingsPath: '/path/to/settings', + clustersPath: '/path/to/clusters', + tagsPath: '/path/to/tags', + projectPath: '/path/to/project', + metricsEndpoint: mockApiEndpoint, + deploymentsEndpoint: null, + emptyGettingStartedSvgPath: '/path/to/getting-started.svg', + emptyLoadingSvgPath: '/path/to/loading.svg', + emptyNoDataSvgPath: '/path/to/no-data.svg', + emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg', + emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', + environmentsEndpoint: '/root/hello-prometheus/environments/35', + currentEnvironmentName: 'production', + customMetricsAvailable: false, + customMetricsPath: '', + validateQueryPath: '', +}; + +export const setupComponentStore = wrapper => { + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + metricsGroupsAPIResponse, + ); + + // Load 3 panels to the dashboard, one with an empty result + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedEmptyResult, + ); + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedQueryResultPayload, + ); + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedQueryResultPayloadCoresTotal, + ); + + wrapper.vm.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); +}; diff --git a/spec/javascripts/monitoring/shared/prometheus_header_spec.js b/spec/frontend/monitoring/shared/prometheus_header_spec.js index 9f916a4dfbb..b216bfb72d8 100644 --- a/spec/javascripts/monitoring/shared/prometheus_header_spec.js +++ b/spec/frontend/monitoring/shared/prometheus_header_spec.js @@ -18,7 +18,7 @@ describe('Prometheus Header component', () => { describe('Prometheus header component', () => { it('should show a title', () => { - const title = prometheusHeader.vm.$el.querySelector('.js-graph-title').textContent; + const title = prometheusHeader.find({ ref: 'title' }).text(); expect(title).toBe('graph header'); }); diff --git a/spec/helpers/container_expiration_policies_helper_spec.rb b/spec/helpers/container_expiration_policies_helper_spec.rb index 3eb1234d82b..f7e851fb012 100644 --- a/spec/helpers/container_expiration_policies_helper_spec.rb +++ b/spec/helpers/container_expiration_policies_helper_spec.rb @@ -8,7 +8,7 @@ describe ContainerExpirationPoliciesHelper do expected_result = [ { key: 1, label: '1 tag per image name' }, { key: 5, label: '5 tags per image name' }, - { key: 10, label: '10 tags per image name' }, + { key: 10, label: '10 tags per image name', default: true }, { key: 25, label: '25 tags per image name' }, { key: 50, label: '50 tags per image name' }, { key: 100, label: '100 tags per image name' } @@ -21,7 +21,7 @@ describe ContainerExpirationPoliciesHelper do describe '#cadence_options' do it 'returns cadence options formatted for dropdown usage' do expected_result = [ - { key: '1d', label: 'Every day' }, + { key: '1d', label: 'Every day', default: true }, { key: '7d', label: 'Every week' }, { key: '14d', label: 'Every two weeks' }, { key: '1month', label: 'Every month' }, @@ -37,7 +37,7 @@ describe ContainerExpirationPoliciesHelper do expected_result = [ { key: '7d', label: '7 days until tags are automatically removed' }, { key: '14d', label: '14 days until tags are automatically removed' }, - { key: '30d', label: '30 days until tags are automatically removed' }, + { key: '30d', label: '30 days until tags are automatically removed', default: true }, { key: '90d', label: '90 days until tags are automatically removed' } ] diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 46228d0d1c2..c7e454771bb 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -332,13 +332,13 @@ describe ProjectsHelper do end it 'returns image tag for member avatar' do - expect(helper).to receive(:image_tag).with(expected, { width: 16, class: ["avatar", "avatar-inline", "s16"], alt: "", "data-src" => anything }) + expect(helper).to receive(:image_tag).with(expected, { width: 16, class: %w[avatar avatar-inline s16], alt: "", "data-src" => anything }) helper.link_to_member_avatar(user) end it 'returns image tag with avatar class' do - expect(helper).to receive(:image_tag).with(expected, { width: 16, class: ["avatar", "avatar-inline", "s16", "any-avatar-class"], alt: "", "data-src" => anything }) + expect(helper).to receive(:image_tag).with(expected, { width: 16, class: %w[avatar avatar-inline s16 any-avatar-class], alt: "", "data-src" => anything }) helper.link_to_member_avatar(user, avatar_class: "any-avatar-class") end diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index e471be608c8..1d91b508b71 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -88,10 +88,12 @@ describe('common_utils', () => { describe('handleLocationHash', () => { beforeEach(() => { spyOn(window.document, 'getElementById').and.callThrough(); + jasmine.clock().install(); }); afterEach(() => { window.history.pushState({}, null, ''); + jasmine.clock().uninstall(); }); function expectGetElementIdToHaveBeenCalledWith(elementId) { @@ -171,6 +173,7 @@ describe('common_utils', () => { window.history.pushState({}, null, '#test'); commonUtils.handleLocationHash(); + jasmine.clock().tick(1); expectGetElementIdToHaveBeenCalledWith('test'); expectGetElementIdToHaveBeenCalledWith('user-content-test'); diff --git a/spec/javascripts/monitoring/components/dashboard_resize_spec.js b/spec/javascripts/monitoring/components/dashboard_resize_spec.js new file mode 100644 index 00000000000..4eab398e3ab --- /dev/null +++ b/spec/javascripts/monitoring/components/dashboard_resize_spec.js @@ -0,0 +1,140 @@ +import Vue from 'vue'; +import { createLocalVue } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import Dashboard from '~/monitoring/components/dashboard.vue'; +import * as types from '~/monitoring/stores/mutation_types'; +import { createStore } from '~/monitoring/stores'; +import axios from '~/lib/utils/axios_utils'; +import { + metricsGroupsAPIResponse, + mockedEmptyResult, + mockedQueryResultPayload, + mockedQueryResultPayloadCoresTotal, + mockApiEndpoint, + environmentData, +} from '../mock_data'; + +const localVue = createLocalVue(); +const propsData = { + hasMetrics: false, + documentationPath: '/path/to/docs', + settingsPath: '/path/to/settings', + clustersPath: '/path/to/clusters', + tagsPath: '/path/to/tags', + projectPath: '/path/to/project', + metricsEndpoint: mockApiEndpoint, + deploymentsEndpoint: null, + emptyGettingStartedSvgPath: '/path/to/getting-started.svg', + emptyLoadingSvgPath: '/path/to/loading.svg', + emptyNoDataSvgPath: '/path/to/no-data.svg', + emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg', + emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', + environmentsEndpoint: '/root/hello-prometheus/environments/35', + currentEnvironmentName: 'production', + customMetricsAvailable: false, + customMetricsPath: '', + validateQueryPath: '', +}; + +function setupComponentStore(component) { + // Load 2 panel groups + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + metricsGroupsAPIResponse, + ); + + // Load 3 panels to the dashboard, one with an empty result + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedEmptyResult, + ); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedQueryResultPayload, + ); + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedQueryResultPayloadCoresTotal, + ); + + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); +} + +describe('Dashboard', () => { + let DashboardComponent; + let mock; + let store; + let component; + let wrapper; + + beforeEach(() => { + setFixtures(` + <div class="prometheus-graphs"></div> + <div class="layout-page"></div> + `); + + store = createStore(); + mock = new MockAdapter(axios); + DashboardComponent = localVue.extend(Dashboard); + }); + + afterEach(() => { + if (component) { + component.$destroy(); + } + if (wrapper) { + wrapper.destroy(); + } + mock.restore(); + }); + + describe('responds to window resizes', () => { + let promPanel; + let promGroup; + let panelToggle; + let chart; + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: true, + }, + store, + }); + + setupComponentStore(component); + + return Vue.nextTick().then(() => { + [, promPanel] = component.$el.querySelectorAll('.prometheus-panel'); + promGroup = promPanel.querySelector('.prometheus-graph-group'); + panelToggle = promPanel.querySelector('.js-graph-group-toggle'); + chart = promGroup.querySelector('.position-relative svg'); + }); + }); + + it('setting chart size to zero when panel group is hidden', () => { + expect(promGroup.style.display).toBe(''); + expect(chart.clientWidth).toBeGreaterThan(0); + + panelToggle.click(); + return Vue.nextTick().then(() => { + expect(promGroup.style.display).toBe('none'); + expect(chart.clientWidth).toBe(0); + promPanel.style.width = '500px'; + }); + }); + + it('expanding chart panel group after resize displays chart', () => { + panelToggle.click(); + + expect(chart.clientWidth).toBeGreaterThan(0); + }); + }); +}); diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js deleted file mode 100644 index b29bac21820..00000000000 --- a/spec/javascripts/monitoring/components/dashboard_spec.js +++ /dev/null @@ -1,729 +0,0 @@ -import Vue from 'vue'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlToast } from '@gitlab/ui'; -import VueDraggable from 'vuedraggable'; -import MockAdapter from 'axios-mock-adapter'; -import Dashboard from '~/monitoring/components/dashboard.vue'; -import { metricStates } from '~/monitoring/constants'; -import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; -import * as types from '~/monitoring/stores/mutation_types'; -import { createStore } from '~/monitoring/stores'; -import axios from '~/lib/utils/axios_utils'; -import { - metricsGroupsAPIResponse, - mockedEmptyResult, - mockedQueryResultPayload, - mockedQueryResultPayloadCoresTotal, - mockApiEndpoint, - environmentData, - dashboardGitResponse, -} from '../mock_data'; - -const localVue = createLocalVue(); -const propsData = { - hasMetrics: false, - documentationPath: '/path/to/docs', - settingsPath: '/path/to/settings', - clustersPath: '/path/to/clusters', - tagsPath: '/path/to/tags', - projectPath: '/path/to/project', - metricsEndpoint: mockApiEndpoint, - deploymentsEndpoint: null, - emptyGettingStartedSvgPath: '/path/to/getting-started.svg', - emptyLoadingSvgPath: '/path/to/loading.svg', - emptyNoDataSvgPath: '/path/to/no-data.svg', - emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg', - emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', - environmentsEndpoint: '/root/hello-prometheus/environments/35', - currentEnvironmentName: 'production', - customMetricsAvailable: false, - customMetricsPath: '', - validateQueryPath: '', -}; - -const resetSpy = spy => { - if (spy) { - spy.calls.reset(); - } -}; - -let expectedPanelCount; - -function setupComponentStore(component) { - // Load 2 panel groups - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, - metricsGroupsAPIResponse, - ); - - // Load 3 panels to the dashboard, one with an empty result - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, - mockedEmptyResult, - ); - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, - mockedQueryResultPayload, - ); - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, - mockedQueryResultPayloadCoresTotal, - ); - - expectedPanelCount = 2; - - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, - environmentData, - ); -} - -describe('Dashboard', () => { - let DashboardComponent; - let mock; - let store; - let component; - let wrapper; - - const createComponentWrapper = (props = {}, options = {}) => { - wrapper = shallowMount(localVue.extend(DashboardComponent), { - localVue, - sync: false, - propsData: { ...propsData, ...props }, - store, - ...options, - }); - }; - - beforeEach(() => { - setFixtures(` - <div class="prometheus-graphs"></div> - <div class="layout-page"></div> - `); - - store = createStore(); - mock = new MockAdapter(axios); - DashboardComponent = localVue.extend(Dashboard); - }); - - afterEach(() => { - if (component) { - component.$destroy(); - } - if (wrapper) { - wrapper.destroy(); - } - mock.restore(); - }); - - describe('no metrics are available yet', () => { - beforeEach(() => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData }, - store, - }); - }); - - it('shows a getting started empty state when no metrics are present', () => { - expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); - expect(component.emptyState).toEqual('gettingStarted'); - }); - - it('shows the environment selector', () => { - expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); - }); - }); - - describe('no data found', () => { - it('shows the environment selector dropdown', () => { - createComponentWrapper(); - - expect(wrapper.find('.js-environments-dropdown').exists()).toBeTruthy(); - }); - }); - - describe('cluster health', () => { - beforeEach(done => { - createComponentWrapper({ hasMetrics: true }); - - // all_dashboards is not defined in health dashboards - wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined); - wrapper.vm.$nextTick(done); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders correctly', () => { - expect(wrapper.isVueInstance()).toBe(true); - expect(wrapper.exists()).toBe(true); - }); - }); - - describe('requests information to the server', () => { - let spy; - beforeEach(() => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - }); - - afterEach(() => { - resetSpy(spy); - }); - - it('shows up a loading state', done => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true }, - store, - }); - - Vue.nextTick(() => { - expect(component.emptyState).toEqual('loading'); - done(); - }); - }); - - it('hides the group panels when showPanels is false', done => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - }, - store, - }); - - setupComponentStore(component); - - Vue.nextTick() - .then(() => { - expect(component.showEmptyState).toEqual(false); - expect(component.$el.querySelector('.prometheus-panel')).toEqual(null); - expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy(); - - done(); - }) - .catch(done.fail); - }); - - describe('when all the requests have been commited by the store', () => { - beforeEach(() => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - }, - store, - }); - - setupComponentStore(component); - }); - - it('renders the environments dropdown with a number of environments', done => { - Vue.nextTick() - .then(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item', - ); - - expect(component.environments.length).toEqual(environmentData.length); - expect(dropdownMenuEnvironments.length).toEqual(component.environments.length); - - Array.from(dropdownMenuEnvironments).forEach((value, index) => { - if (environmentData[index].metrics_path) { - expect(value).toHaveAttr('href', environmentData[index].metrics_path); - } - }); - - done(); - }) - .catch(done.fail); - }); - - it('renders the environments dropdown with a single active element', done => { - Vue.nextTick() - .then(() => { - const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item.active', - ); - - expect(dropdownItems.length).toEqual(1); - done(); - }) - .catch(done.fail); - }); - }); - - it('hides the environments dropdown list when there is no environments', done => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - }, - store, - }); - - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, - metricsGroupsAPIResponse, - ); - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, - mockedQueryResultPayload, - ); - - Vue.nextTick() - .then(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item', - ); - - expect(dropdownMenuEnvironments.length).toEqual(0); - done(); - }) - .catch(done.fail); - }); - - it('renders the datetimepicker dropdown', done => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - }, - store, - }); - - setupComponentStore(component); - - Vue.nextTick() - .then(() => { - expect(component.$el.querySelector('.js-time-window-dropdown')).not.toBeNull(); - done(); - }) - .catch(done.fail); - }); - - it('fetches the metrics data with proper time window', done => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - }, - store, - }); - - spyOn(component.$store, 'dispatch').and.stub(); - const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough(); - - component.$store.commit( - `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, - environmentData, - ); - - component.$mount(); - - Vue.nextTick() - .then(() => { - expect(component.$store.dispatch).toHaveBeenCalled(); - expect(getTimeDiffSpy).toHaveBeenCalled(); - - done(); - }) - .catch(done.fail); - }); - - it('shows a specific time window selected from the url params', done => { - const start = '2019-10-01T18:27:47.000Z'; - const end = '2019-10-01T18:57:47.000Z'; - spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({ - start, - end, - }); - spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => { - if (param === 'start') return [start]; - if (param === 'end') return [end]; - return []; - }); - - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true }, - store, - sync: false, - }); - - setupComponentStore(component); - - Vue.nextTick() - .then(() => { - const selectedTimeWindow = component.$el.querySelector( - '.js-time-window-dropdown .active', - ); - - expect(selectedTimeWindow.textContent.trim()).toEqual('30 minutes'); - done(); - }) - .catch(done.fail); - }); - - it('shows an error message if invalid url parameters are passed', done => { - spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([ - '<script>alert("XSS")</script>', - ]); - - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true }, - store, - }); - - spy = spyOn(component, 'showInvalidDateError'); - component.$mount(); - - component.$nextTick(() => { - expect(component.showInvalidDateError).toHaveBeenCalled(); - done(); - }); - }); - }); - - describe('when one of the metrics is missing', () => { - beforeEach(() => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - }); - - beforeEach(done => { - createComponentWrapper({ hasMetrics: true }); - setupComponentStore(wrapper.vm); - - wrapper.vm.$nextTick(done); - }); - - it('shows a group empty area', () => { - const emptyGroup = wrapper.findAll({ ref: 'empty-group' }); - - expect(emptyGroup).toHaveLength(1); - expect(emptyGroup.is(GroupEmptyState)).toBe(true); - }); - - it('group empty area displays a NO_DATA state', () => { - expect( - wrapper - .findAll({ ref: 'empty-group' }) - .at(0) - .props('selectedState'), - ).toEqual(metricStates.NO_DATA); - }); - }); - - describe('drag and drop function', () => { - const findDraggables = () => wrapper.findAll(VueDraggable); - const findEnabledDraggables = () => findDraggables().filter(f => !f.attributes('disabled')); - const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel'); - const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); - - beforeEach(() => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - }); - - beforeEach(done => { - createComponentWrapper({ hasMetrics: true }, { attachToDocument: true }); - - setupComponentStore(wrapper.vm); - - wrapper.vm.$nextTick(done); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('wraps vuedraggable', () => { - expect(findDraggablePanels().exists()).toBe(true); - expect(findDraggablePanels().length).toEqual(expectedPanelCount); - }); - - it('is disabled by default', () => { - expect(findRearrangeButton().exists()).toBe(false); - expect(findEnabledDraggables().length).toBe(0); - }); - - describe('when rearrange is enabled', () => { - beforeEach(done => { - wrapper.setProps({ rearrangePanelsAvailable: true }); - wrapper.vm.$nextTick(done); - }); - - it('displays rearrange button', () => { - expect(findRearrangeButton().exists()).toBe(true); - }); - - describe('when rearrange button is clicked', () => { - const findFirstDraggableRemoveButton = () => - findDraggablePanels() - .at(0) - .find('.js-draggable-remove'); - - beforeEach(done => { - findRearrangeButton().vm.$emit('click'); - wrapper.vm.$nextTick(done); - }); - - it('it enables draggables', () => { - expect(findRearrangeButton().attributes('pressed')).toBeTruthy(); - expect(findEnabledDraggables()).toEqual(findDraggables()); - }); - - it('metrics can be swapped', done => { - const firstDraggable = findDraggables().at(0); - const mockMetrics = [...metricsGroupsAPIResponse[1].panels]; - - const firstTitle = mockMetrics[0].title; - const secondTitle = mockMetrics[1].title; - - // swap two elements and `input` them - [mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]]; - firstDraggable.vm.$emit('input', mockMetrics); - - wrapper.vm.$nextTick(() => { - const { panels } = wrapper.vm.dashboard.panel_groups[1]; - - expect(panels[1].title).toEqual(firstTitle); - expect(panels[0].title).toEqual(secondTitle); - done(); - }); - }); - - it('shows a remove button, which removes a panel', done => { - expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false); - - expect(findDraggablePanels().length).toEqual(expectedPanelCount); - findFirstDraggableRemoveButton().trigger('click'); - - wrapper.vm.$nextTick(() => { - expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1); - done(); - }); - }); - - it('it disables draggables when clicked again', done => { - findRearrangeButton().vm.$emit('click'); - wrapper.vm.$nextTick(() => { - expect(findRearrangeButton().attributes('pressed')).toBeFalsy(); - expect(findEnabledDraggables().length).toBe(0); - done(); - }); - }); - }); - }); - }); - - // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922 - // eslint-disable-next-line jasmine/no-disabled-tests - xdescribe('link to chart', () => { - const currentDashboard = 'TEST_DASHBOARD'; - localVue.use(GlToast); - const link = () => wrapper.find('.js-chart-link'); - const clipboardText = () => link().element.dataset.clipboardText; - - beforeEach(done => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - - createComponentWrapper({ hasMetrics: true, currentDashboard }, { attachToDocument: true }); - - setTimeout(done); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('adds a copy button to the dropdown', () => { - expect(link().text()).toContain('Generate link to chart'); - }); - - it('contains a link to the dashboard', () => { - expect(clipboardText()).toContain(`dashboard=${currentDashboard}`); - expect(clipboardText()).toContain(`group=`); - expect(clipboardText()).toContain(`title=`); - expect(clipboardText()).toContain(`y_label=`); - }); - - it('undefined parameter is stripped', done => { - wrapper.setProps({ currentDashboard: undefined }); - - wrapper.vm.$nextTick(() => { - expect(clipboardText()).not.toContain(`dashboard=`); - expect(clipboardText()).toContain(`y_label=`); - done(); - }); - }); - - it('null parameter is stripped', done => { - wrapper.setProps({ currentDashboard: null }); - - wrapper.vm.$nextTick(() => { - expect(clipboardText()).not.toContain(`dashboard=`); - expect(clipboardText()).toContain(`y_label=`); - done(); - }); - }); - - it('creates a toast when clicked', () => { - spyOn(wrapper.vm.$toast, 'show').and.stub(); - - link().vm.$emit('click'); - - expect(wrapper.vm.$toast.show).toHaveBeenCalled(); - }); - }); - - describe('responds to window resizes', () => { - let promPanel; - let promGroup; - let panelToggle; - let chart; - beforeEach(() => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: true, - }, - store, - }); - - setupComponentStore(component); - - return Vue.nextTick().then(() => { - [, promPanel] = component.$el.querySelectorAll('.prometheus-panel'); - promGroup = promPanel.querySelector('.prometheus-graph-group'); - panelToggle = promPanel.querySelector('.js-graph-group-toggle'); - chart = promGroup.querySelector('.position-relative svg'); - }); - }); - - it('setting chart size to zero when panel group is hidden', () => { - expect(promGroup.style.display).toBe(''); - expect(chart.clientWidth).toBeGreaterThan(0); - - panelToggle.click(); - return Vue.nextTick().then(() => { - expect(promGroup.style.display).toBe('none'); - expect(chart.clientWidth).toBe(0); - promPanel.style.width = '500px'; - }); - }); - - it('expanding chart panel group after resize displays chart', () => { - panelToggle.click(); - - expect(chart.clientWidth).toBeGreaterThan(0); - }); - }); - - describe('dashboard edit link', () => { - const findEditLink = () => wrapper.find('.js-edit-link'); - - beforeEach(done => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - - createComponentWrapper({ hasMetrics: true }, { attachToDocument: true }); - - wrapper.vm.$store.commit( - `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, - dashboardGitResponse, - ); - wrapper.vm.$nextTick(done); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('is not present for the default dashboard', () => { - expect(findEditLink().exists()).toBe(false); - }); - - it('is present for a custom dashboard, and links to its edit_path', done => { - const dashboard = dashboardGitResponse[1]; // non-default dashboard - const currentDashboard = dashboard.path; - - wrapper.setProps({ currentDashboard }); - wrapper.vm.$nextTick(() => { - expect(findEditLink().exists()).toBe(true); - expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path); - done(); - }); - }); - }); - - describe('external dashboard link', () => { - beforeEach(() => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - showTimeWindowDropdown: false, - externalDashboardUrl: '/mockUrl', - }, - store, - }); - }); - - it('shows the link', done => { - setTimeout(() => { - expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain( - 'View full dashboard', - ); - done(); - }); - }); - }); - - describe('Dashboard dropdown', () => { - beforeEach(() => { - mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); - - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - }, - store, - }); - - component.$store.commit( - `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, - dashboardGitResponse, - ); - }); - - it('shows the dashboard dropdown', done => { - setTimeout(() => { - const dashboardDropdown = component.$el.querySelector('.js-dashboards-dropdown'); - - expect(dashboardDropdown).not.toEqual(null); - done(); - }); - }); - }); -}); diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb index 394efa85701..1b8ec2b1979 100644 --- a/spec/lib/expand_variables_spec.rb +++ b/spec/lib/expand_variables_spec.rb @@ -100,7 +100,7 @@ describe ExpandVariables do end with_them do - subject { ExpandVariables.expand(value, variables) } # rubocop:disable RSpec/DescribedClass + subject { ExpandVariables.expand(value, variables) } it { is_expected.to eq(result) } end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 38ec04ebe81..ba5b70b44de 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -481,7 +481,6 @@ module Gitlab ['../sample.adoc', 'doc/sample.adoc', 'relative path to a file up one directory'], ['../../sample.adoc', 'sample.adoc', 'relative path for a file up multiple directories'] ].each do |include_path_, file_path_, desc| - context "the file is specified by #{desc}" do let(:include_path) { include_path_ } let(:file_path) { file_path_ } diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 4fa0a57dc82..f7b14360af3 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -31,13 +31,13 @@ describe Gitlab::Ci::Config::Entry::Cache do it_behaves_like 'hash key value' context 'with files' do - let(:key) { { files: ['a-file', 'other-file'] } } + let(:key) { { files: %w[a-file other-file] } } it_behaves_like 'hash key value' end context 'with files and prefix' do - let(:key) { { files: ['a-file', 'other-file'], prefix: 'prefix-value' } } + let(:key) { { files: %w[a-file other-file], prefix: 'prefix-value' } } it_behaves_like 'hash key value' end @@ -55,7 +55,7 @@ describe Gitlab::Ci::Config::Entry::Cache do it { is_expected.to be_valid } context 'with files' do - let(:key) { { files: ['a-file', 'other-file'] } } + let(:key) { { files: %w[a-file other-file] } } it { is_expected.to be_valid } end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb index b09258ae227..56767c21ab7 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb @@ -6,12 +6,12 @@ shared_examples 'renames child namespaces' do |type| it 'renames namespaces' do rename_namespaces = double expect(described_class::RenameNamespaces) - .to receive(:new).with(['first-path', 'second-path'], subject) + .to receive(:new).with(%w[first-path second-path], subject) .and_return(rename_namespaces) expect(rename_namespaces).to receive(:rename_namespaces) .with(type: :child) - subject.rename_wildcard_paths(['first-path', 'second-path']) + subject.rename_wildcard_paths(%w[first-path second-path]) end end diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index b8be72cf8d7..e4624accd58 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -54,7 +54,7 @@ describe Gitlab::Experimentation do describe '#experiment_enabled?' do context 'cookie is not present' do it 'calls Gitlab::Experimentation.enabled_for_user? with the name of the experiment and an experimentation_subject_index of nil' do - expect(Gitlab::Experimentation).to receive(:enabled_for_user?).with(:test_experiment, nil) # rubocop:disable RSpec/DescribedClass + expect(Gitlab::Experimentation).to receive(:enabled_for_user?).with(:test_experiment, nil) controller.experiment_enabled?(:test_experiment) end end @@ -67,7 +67,7 @@ describe Gitlab::Experimentation do it 'calls Gitlab::Experimentation.enabled_for_user? with the name of the experiment and an experimentation_subject_index of the modulo 100 of the hex value of the uuid' do # 'abcd1234'.hex % 100 = 76 - expect(Gitlab::Experimentation).to receive(:enabled_for_user?).with(:test_experiment, 76) # rubocop:disable RSpec/DescribedClass + expect(Gitlab::Experimentation).to receive(:enabled_for_user?).with(:test_experiment, 76) controller.experiment_enabled?(:test_experiment) end end diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb index 887a6baf659..fc6ac491671 100644 --- a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::GitalyClient::BlobService do describe '#get_new_lfs_pointers' do let(:revision) { 'master' } let(:limit) { 5 } - let(:not_in) { ['branch-a', 'branch-b'] } + let(:not_in) { %w[branch-a branch-b] } let(:expected_params) do { revision: revision, limit: limit, not_in_refs: not_in, not_in_all: false } end diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index 929ff5dee5d..73ae4cd95ce 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -69,7 +69,7 @@ describe Gitlab::GitalyClient::RemoteService do describe '#update_remote_mirror' do let(:ref_name) { 'remote_mirror_1' } - let(:only_branches_matching) { ['my-branch', 'master'] } + let(:only_branches_matching) { %w[my-branch master] } let(:ssh_key) { 'KEY' } let(:known_hosts) { 'KNOWN HOSTS' } diff --git a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb index e88eec2c393..f3928f390bc 100644 --- a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb +++ b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb @@ -15,13 +15,13 @@ describe Gitlab::PhabricatorImport::Conduit::User do it 'calls the api with the correct params' do expected_params = { - constraints: { phids: ['phid-1', 'phid-2'] } + constraints: { phids: %w[phid-1 phid-2] } } expect(fake_client).to receive(:get).with('user.search', params: expected_params) - user_client.users(['phid-1', 'phid-2']) + user_client.users(%w[phid-1 phid-2]) end it 'returns an array of parsed responses' do @@ -43,7 +43,7 @@ describe Gitlab::PhabricatorImport::Conduit::User do expect(fake_client).to receive(:get).with('user.search', params: second_params).once - user_client.users(['phid-1', 'phid-2']) + user_client.users(%w[phid-1 phid-2]) end end end diff --git a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb index 14a00deeb16..f260e38b7c8 100644 --- a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb +++ b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do let(:project) { create(:project, namespace: create(:group)) } - subject(:finder) { described_class.new(project, ['first-phid', 'second-phid']) } + subject(:finder) { described_class.new(project, %w[first-phid second-phid]) } before do project.namespace.add_developer(existing_user) diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb index c98c36622f5..1145a7edc85 100644 --- a/spec/lib/gitlab/quick_actions/dsl_spec.rb +++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Gitlab::QuickActions::Dsl do before :all do DummyClass = Struct.new(:project) do - include Gitlab::QuickActions::Dsl # rubocop:disable RSpec/DescribedClass + include Gitlab::QuickActions::Dsl desc 'A command with no args' command :no_args, :none do diff --git a/spec/lib/quality/helm_client_spec.rb b/spec/lib/quality/helm_client_spec.rb index 795aa43b849..8d199fe3531 100644 --- a/spec/lib/quality/helm_client_spec.rb +++ b/spec/lib/quality/helm_client_spec.rb @@ -110,7 +110,7 @@ RSpec.describe Quality::HelmClient do end context 'with multiple release names' do - let(:release_name) { ['my-release', 'my-release-2'] } + let(:release_name) { %w[my-release my-release-2] } it 'raises an error if the Helm command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb index 59d4a977d5e..6a62ef456c1 100644 --- a/spec/lib/quality/kubernetes_client_spec.rb +++ b/spec/lib/quality/kubernetes_client_spec.rb @@ -46,7 +46,7 @@ RSpec.describe Quality::KubernetesClient do end context 'with multiple releases' do - let(:release_name) { ['my-release', 'my-release-2'] } + let(:release_name) { %w[my-release my-release-2] } it 'raises an error if the Kubernetes command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) diff --git a/spec/migrations/fix_max_pages_size_spec.rb b/spec/migrations/fix_max_pages_size_spec.rb new file mode 100644 index 00000000000..36b5445603e --- /dev/null +++ b/spec/migrations/fix_max_pages_size_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20191213120427_fix_max_pages_size.rb') + +describe FixMaxPagesSize, :migration do + let(:application_settings) { table(:application_settings) } + let!(:default_setting) { application_settings.create! } + let!(:max_possible_setting) { application_settings.create!(max_pages_size: described_class::MAX_SIZE) } + let!(:higher_than_maximum_setting) { application_settings.create!(max_pages_size: described_class::MAX_SIZE + 1) } + + it 'correctly updates settings only if needed' do + migrate! + + expect(default_setting.reload.max_pages_size).to eq(100) + expect(max_possible_setting.reload.max_pages_size).to eq(described_class::MAX_SIZE) + expect(higher_than_maximum_setting.reload.max_pages_size).to eq(described_class::MAX_SIZE) + end +end diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index 072d0fa86e5..6930f743c2f 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -139,7 +139,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do redis = double(:redis) expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis) - sessions = ['session-a', 'session-b'] + sessions = %w[session-a session-b] mget_responses = sessions.map { |session| [Marshal.dump(session)]} expect(redis).to receive(:mget).twice.and_return(*mget_responses) diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index a403aa296d4..52e60a69a52 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -67,6 +67,13 @@ describe ApplicationSetting do it { is_expected.not_to allow_value(nil).for(:push_event_activities_limit) } it { is_expected.to validate_numericality_of(:snippet_size_limit).only_integer.is_greater_than(0) } + it { is_expected.to validate_presence_of(:max_artifacts_size) } + it do + is_expected.to validate_numericality_of(:max_pages_size).only_integer.is_greater_than(0) + .is_less_than(::Gitlab::Pages::MAX_SIZE / 1.megabyte) + end + it { is_expected.to validate_numericality_of(:max_artifacts_size).only_integer.is_greater_than(0) } + it { is_expected.to validate_numericality_of(:max_pages_size).only_integer.is_greater_than(0) } it { is_expected.not_to allow_value(7).for(:minimum_password_length) } it { is_expected.not_to allow_value(129).for(:minimum_password_length) } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 0f829e138d5..e7f5f493b82 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -130,7 +130,7 @@ describe Clusters::Applications::Prometheus do it 'is initialized with 3 arguments' do expect(subject.name).to eq('prometheus') expect(subject.chart).to eq('stable/prometheus') - expect(subject.version).to eq('6.7.3') + expect(subject.version).to eq('9.5.2') expect(subject).to be_rbac expect(subject.files).to eq(prometheus.files) end @@ -147,7 +147,7 @@ describe Clusters::Applications::Prometheus do let(:prometheus) { create(:clusters_applications_prometheus, :errored, version: '2.0.0') } it 'is initialized with the locked version' do - expect(subject.version).to eq('6.7.3') + expect(subject.version).to eq('9.5.2') end end @@ -218,7 +218,7 @@ describe Clusters::Applications::Prometheus do it 'is initialized with 3 arguments' do expect(patch_command.name).to eq('prometheus') expect(patch_command.chart).to eq('stable/prometheus') - expect(patch_command.version).to eq('6.7.3') + expect(patch_command.version).to eq('9.5.2') expect(patch_command.files).to eq(prometheus.files) end end diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index ef426661066..5b8be7914d4 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -138,8 +138,6 @@ describe ErrorTracking::ProjectErrorTrackingSetting do error: 'error message', error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE ) - expect(subject).to have_received(:sentry_client) - expect(sentry_client).to have_received(:list_issues) end end @@ -159,8 +157,6 @@ describe ErrorTracking::ProjectErrorTrackingSetting do error: 'Sentry API response is missing keys. key not found: "id"', error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS ) - expect(subject).to have_received(:sentry_client) - expect(sentry_client).to have_received(:list_issues) end end @@ -181,8 +177,21 @@ describe ErrorTracking::ProjectErrorTrackingSetting do error: error_msg, error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_INVALID_SIZE ) - expect(subject).to have_received(:sentry_client) - expect(sentry_client).to have_received(:list_issues) + end + end + + context 'when sentry client raises StandardError' do + let(:sentry_client) { spy(:sentry_client) } + + before do + synchronous_reactive_cache(subject) + + allow(subject).to receive(:sentry_client).and_return(sentry_client) + allow(sentry_client).to receive(:list_issues).with(opts).and_raise(StandardError) + end + + it 'returns error' do + expect(result).to eq(error: 'Unexpected Error') end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c98d123ff52..0e151475128 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -384,7 +384,7 @@ describe MergeRequest do end it 'returns target branches sort by updated at desc' do - expect(described_class.recent_target_branches).to match_array(['feature', 'merge-test', 'fix']) + expect(described_class.recent_target_branches).to match_array(%w[feature merge-test fix]) end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 2ba0d97792b..b732412c52c 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -26,6 +26,7 @@ describe Namespace do it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_length_of(:path).is_at_most(255) } it { is_expected.to validate_presence_of(:owner) } + it { is_expected.to validate_numericality_of(:max_artifacts_size).only_integer.is_greater_than(0) } it 'does not allow too deep nesting' do ancestors = (1..21).to_a diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e66f37f2eec..31dc0134410 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -211,6 +211,7 @@ describe Project do it { is_expected.to validate_presence_of(:creator) } it { is_expected.to validate_presence_of(:namespace) } it { is_expected.to validate_presence_of(:repository_storage) } + it { is_expected.to validate_numericality_of(:max_artifacts_size).only_integer.is_greater_than(0) } it 'validates build timeout constraints' do is_expected.to validate_numericality_of(:build_timeout) diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index 310caa92eb9..2e0b7a30480 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -115,7 +115,7 @@ describe API::Wikis do end [:title, :content, :format].each do |part| - it "it updates with wiki with missing #{part}" do + it "updates with wiki with missing #{part}" do payload.delete(part) put(api(url, user), params: payload) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index b80f75c70e6..5440a42348e 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2707,7 +2707,7 @@ describe NotificationService, :mailer do # User to be participant by default # This user does not contain any record in notification settings table # It should be treated with a :participating notification_level - @u_lazy_participant = create(:user, username: 'lazy-participant') + @u_lazy_participant = create(:user, username: 'lazy-participant') @u_guest_watcher = create_user_with_notification(:watch, 'guest_watching') @u_guest_custom = create_user_with_notification(:custom, 'guest_custom') diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index fe92b53cd91..9aa8c7f85ca 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -185,60 +185,20 @@ describe Projects::UpdatePagesService do .and_return(metadata) end - shared_examples 'pages size limit exceeded' do - it 'limits the maximum size of gitlab pages' do - subject.execute - - expect(deploy_status.description) - .to match(/artifacts for pages are too large/) - expect(deploy_status).to be_script_failure - expect(project.pages_metadatum).not_to be_deployed - end - end - context 'when maximum pages size is set to zero' do before do stub_application_setting(max_pages_size: 0) end - context 'when page size does not exceed internal maximum' do - before do - allow(metadata).to receive(:total_size).and_return(200.megabytes) - end - - it 'updates pages correctly' do - subject.execute - - expect(deploy_status.description).not_to be_present - expect(project.pages_metadatum).to be_deployed - end - end - - context 'when pages size does exceed internal maximum' do - before do - allow(metadata).to receive(:total_size).and_return(2.terabytes) - end - - it_behaves_like 'pages size limit exceeded' - end - end - - context 'when pages size is greater than max size setting' do - before do - stub_application_setting(max_pages_size: 200) - allow(metadata).to receive(:total_size).and_return(201.megabytes) - end - - it_behaves_like 'pages size limit exceeded' + it_behaves_like 'pages size limit is', ::Gitlab::Pages::MAX_SIZE end - context 'when max size setting is greater than internal max size' do + context 'when size is limited on the instance level' do before do - stub_application_setting(max_pages_size: 3.terabytes / 1.megabyte) - allow(metadata).to receive(:total_size).and_return(2.terabytes) + stub_application_setting(max_pages_size: 100) end - it_behaves_like 'pages size limit exceeded' + it_behaves_like 'pages size limit is', 100.megabytes end end diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index f070243f111..ea13e91860a 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -297,11 +297,11 @@ shared_examples 'thread comments' do |resource_name| find("#{form_selector} .note-textarea").send_keys('a') end - it "should show a 'Comment & reopen #{resource_name}' button" do + it "shows a 'Comment & reopen #{resource_name}' button" do expect(find("#{form_selector} .js-note-target-reopen")).to have_content "Comment & reopen #{resource_name}" end - it "should show a 'Start thread & reopen #{resource_name}' button when 'Start thread' is selected" do + it "shows a 'Start thread & reopen #{resource_name}' button when 'Start thread' is selected" do find(toggle_selector).click find("#{menu_selector} li", match: :first) diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 6a23875f103..f2fa6af6402 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -154,7 +154,6 @@ module TestEnv install_dir: gitaly_dir, version: Gitlab::GitalyClient.expected_server_version, task: "gitlab:gitaly:install[#{install_gitaly_args}]") do - Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true) start_gitaly(gitaly_dir) end diff --git a/spec/support/shared_examples/pages_size_limit_shared_examples.rb b/spec/support/shared_examples/pages_size_limit_shared_examples.rb new file mode 100644 index 00000000000..c1e27194738 --- /dev/null +++ b/spec/support/shared_examples/pages_size_limit_shared_examples.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +shared_examples 'pages size limit is' do |size_limit| + context "when size is below the limit" do + before do + allow(metadata).to receive(:total_size).and_return(size_limit - 1.megabyte) + end + + it 'updates pages correctly' do + subject.execute + + expect(deploy_status.description).not_to be_present + expect(project.pages_metadatum).to be_deployed + end + end + + context "when size is above the limit" do + before do + allow(metadata).to receive(:total_size).and_return(size_limit + 1.megabyte) + end + + it 'limits the maximum size of gitlab pages' do + subject.execute + + expect(deploy_status.description) + .to match(/artifacts for pages are too large/) + expect(deploy_status).to be_script_failure + end + end +end diff --git a/spec/views/projects/commit/branches.html.haml_spec.rb b/spec/views/projects/commit/branches.html.haml_spec.rb index 36da489a84f..0fe7165a790 100644 --- a/spec/views/projects/commit/branches.html.haml_spec.rb +++ b/spec/views/projects/commit/branches.html.haml_spec.rb @@ -11,7 +11,7 @@ describe 'projects/commit/branches.html.haml' do context 'when branches and tags are available' do before do - assign(:branches, ['master', 'test-branch']) + assign(:branches, %w[master test-branch]) assign(:branches_limit_exceeded, false) assign(:tags, ['tag1']) assign(:tags_limit_exceeded, false) @@ -35,7 +35,7 @@ describe 'projects/commit/branches.html.haml' do context 'when branches are available but no tags' do before do - assign(:branches, ['master', 'test-branch']) + assign(:branches, %w[master test-branch]) assign(:branches_limit_exceeded, false) assign(:tags, []) assign(:tags_limit_exceeded, true) |