diff options
Diffstat (limited to 'spec')
11 files changed, 342 insertions, 19 deletions
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index c05bf1a547d..7d5a08bc4a1 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -1,4 +1,4 @@ -import { mount } from '@vue/test-utils'; +import { mount, shallowMount } from '@vue/test-utils'; import { setTestTimeout } from 'helpers/timeout'; import { GlLink } from '@gitlab/ui'; import { TEST_HOST } from 'jest/helpers/test_constants'; @@ -11,7 +11,7 @@ import { import { cloneDeep } from 'lodash'; import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper'; import { createStore } from '~/monitoring/stores'; -import { panelTypes } from '~/monitoring/constants'; +import { panelTypes, chartHeight } from '~/monitoring/constants'; import TimeSeries from '~/monitoring/components/charts/time_series.vue'; import * as types from '~/monitoring/stores/mutation_types'; import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data'; @@ -40,10 +40,10 @@ describe('Time series component', () => { let mockGraphData; let store; - const makeTimeSeriesChart = (graphData, type) => - mount(TimeSeries, { + const createWrapper = (graphData = mockGraphData, mountingMethod = shallowMount) => + mountingMethod(TimeSeries, { propsData: { - graphData: { ...graphData, type }, + graphData, deploymentData: store.state.monitoringDashboard.deploymentData, annotations: store.state.monitoringDashboard.annotations, projectPath: `${TEST_HOST}${mockProjectDir}`, @@ -80,9 +80,9 @@ describe('Time series component', () => { const findChart = () => timeSeriesChart.find({ ref: 'chart' }); - beforeEach(done => { - timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); - timeSeriesChart.vm.$nextTick(done); + beforeEach(() => { + timeSeriesChart = createWrapper(mockGraphData, mount); + return timeSeriesChart.vm.$nextTick(); }); it('allows user to override max value label text using prop', () => { @@ -101,6 +101,21 @@ describe('Time series component', () => { }); }); + it('chart sets a default height', () => { + const wrapper = createWrapper(); + expect(wrapper.props('height')).toBe(chartHeight); + }); + + it('chart has a configurable height', () => { + const mockHeight = 599; + const wrapper = createWrapper(); + + wrapper.setProps({ height: mockHeight }); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.props('height')).toBe(mockHeight); + }); + }); + describe('events', () => { describe('datazoom', () => { let eChartMock; @@ -126,7 +141,7 @@ describe('Time series component', () => { }), }; - timeSeriesChart = makeTimeSeriesChart(mockGraphData); + timeSeriesChart = createWrapper(mockGraphData, mount); timeSeriesChart.vm.$nextTick(() => { findChart().vm.$emit('created', eChartMock); done(); @@ -551,7 +566,10 @@ describe('Time series component', () => { const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component); beforeEach(done => { - timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); + timeSeriesAreaChart = createWrapper( + { ...mockGraphData, type: dynamicComponent.chartType }, + mount, + ); timeSeriesAreaChart.vm.$nextTick(done); }); @@ -633,7 +651,7 @@ describe('Time series component', () => { Object.assign(metric, { result: metricResultStatus.result }), ); - timeSeriesChart = makeTimeSeriesChart(graphData, 'area-chart'); + timeSeriesChart = createWrapper({ ...graphData, type: 'area-chart' }, mount); timeSeriesChart.vm.$nextTick(done); }); diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js index 3c0292e016d..d440c063dd4 100644 --- a/spec/frontend/monitoring/components/dashboard_panel_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js @@ -52,11 +52,11 @@ describe('Dashboard Panel', () => { const exampleText = 'example_text'; const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' }); - const findTimeChart = () => wrapper.find({ ref: 'timeChart' }); + const findTimeChart = () => wrapper.find({ ref: 'timeSeriesChart' }); const findTitle = () => wrapper.find({ ref: 'graphTitle' }); const findContextualMenu = () => wrapper.find({ ref: 'contextualMenu' }); - const createWrapper = props => { + const createWrapper = (props, options = {}) => { wrapper = shallowMount(DashboardPanel, { propsData: { graphData, @@ -64,6 +64,7 @@ describe('Dashboard Panel', () => { }, store, mocks, + ...options, }); }; @@ -80,6 +81,22 @@ describe('Dashboard Panel', () => { axiosMock.reset(); }); + describe('Renders slots', () => { + it('renders "topLeft" slot', () => { + createWrapper( + {}, + { + slots: { + topLeft: `<div class="top-left-content">OK</div>`, + }, + }, + ); + + expect(wrapper.find('.top-left-content').exists()).toBe(true); + expect(wrapper.find('.top-left-content').text()).toBe('OK'); + }); + }); + describe('When no graphData is available', () => { beforeEach(() => { createWrapper({ @@ -111,7 +128,7 @@ describe('Dashboard Panel', () => { }); }); - describe('when graph data is available', () => { + describe('When graphData is available', () => { beforeEach(() => { createWrapper(); }); @@ -182,10 +199,13 @@ describe('Dashboard Panel', () => { ${singleStatMetricsResult} | ${MonitorSingleStatChart} ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart} ${barMockData} | ${MonitorBarChart} - `('type $data.type renders the expected component', ({ data, component }) => { - createWrapper({ graphData: data }); + `('wrapps a $data.type component binding attributes', ({ data, component }) => { + const attrs = { attr1: 'attr1Value', attr2: 'attr2Value' }; + createWrapper({ graphData: data }, { attrs }); + expect(wrapper.find(component).exists()).toBe(true); expect(wrapper.find(component).isVueInstance()).toBe(true); + expect(wrapper.find(component).attributes()).toMatchObject(attrs); }); }); }); @@ -436,6 +456,32 @@ describe('Dashboard Panel', () => { }); }); + describe('Expand to full screen', () => { + const findExpandBtn = () => wrapper.find({ ref: 'expandBtn' }); + + describe('when there is no @expand listener', () => { + it('does not show `View full screen` option', () => { + createWrapper(); + expect(findExpandBtn().exists()).toBe(false); + }); + }); + + describe('when there is an @expand listener', () => { + beforeEach(() => { + createWrapper({}, { listeners: { expand: () => {} } }); + }); + + it('shows the `expand` option', () => { + expect(findExpandBtn().exists()).toBe(true); + }); + + it('emits the `expand` event', () => { + findExpandBtn().vm.$emit('click'); + expect(wrapper.emitted('expand')).toHaveLength(1); + }); + }); + }); + describe('panel alerts', () => { const setMetricsSavedToDb = val => monitoringDashboard.getters.metricsSavedToDb.mockReturnValue(val); diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js index 7ee2a16b4bd..fe5754e1216 100644 --- a/spec/frontend/monitoring/store/utils_spec.js +++ b/spec/frontend/monitoring/store/utils_spec.js @@ -27,6 +27,7 @@ describe('mapToDashboardViewModel', () => { group: 'Group 1', panels: [ { + id: 'ID_ABC', title: 'Title A', xLabel: '', xAxis: { @@ -49,6 +50,7 @@ describe('mapToDashboardViewModel', () => { key: 'group-1-0', panels: [ { + id: 'ID_ABC', title: 'Title A', type: 'chart-type', xLabel: '', @@ -127,11 +129,13 @@ describe('mapToDashboardViewModel', () => { it('panel with x_label', () => { setupWithPanel({ + id: 'ID_123', title: panelTitle, x_label: 'x label', }); expect(getMappedPanel()).toEqual({ + id: 'ID_123', title: panelTitle, xLabel: 'x label', xAxis: { @@ -149,10 +153,12 @@ describe('mapToDashboardViewModel', () => { it('group y_axis defaults', () => { setupWithPanel({ + id: 'ID_456', title: panelTitle, }); expect(getMappedPanel()).toEqual({ + id: 'ID_456', title: panelTitle, xLabel: '', y_label: '', diff --git a/spec/frontend/reports/components/__snapshots__/issue_status_icon_spec.js.snap b/spec/frontend/reports/components/__snapshots__/issue_status_icon_spec.js.snap new file mode 100644 index 00000000000..70e1ff01323 --- /dev/null +++ b/spec/frontend/reports/components/__snapshots__/issue_status_icon_spec.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IssueStatusIcon renders "failed" state correctly 1`] = ` +<div + class="report-block-list-icon failed" +> + <icon-stub + data-qa-selector="status_failed_icon" + name="status_failed_borderless" + size="24" + /> +</div> +`; + +exports[`IssueStatusIcon renders "neutral" state correctly 1`] = ` +<div + class="report-block-list-icon neutral" +> + <icon-stub + data-qa-selector="status_neutral_icon" + name="dash" + size="24" + /> +</div> +`; + +exports[`IssueStatusIcon renders "success" state correctly 1`] = ` +<div + class="report-block-list-icon success" +> + <icon-stub + data-qa-selector="status_success_icon" + name="status_success_borderless" + size="24" + /> +</div> +`; diff --git a/spec/frontend/reports/components/issue_status_icon_spec.js b/spec/frontend/reports/components/issue_status_icon_spec.js new file mode 100644 index 00000000000..3a55ff0a9e3 --- /dev/null +++ b/spec/frontend/reports/components/issue_status_icon_spec.js @@ -0,0 +1,29 @@ +import { shallowMount } from '@vue/test-utils'; +import ReportItem from '~/reports/components/issue_status_icon.vue'; +import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants'; + +describe('IssueStatusIcon', () => { + let wrapper; + + const createComponent = ({ status }) => { + wrapper = shallowMount(ReportItem, { + propsData: { + status, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it.each([STATUS_SUCCESS, STATUS_NEUTRAL, STATUS_FAILED])( + 'renders "%s" state correctly', + status => { + createComponent({ status }); + + expect(wrapper.element).toMatchSnapshot(); + }, + ); +}); diff --git a/spec/helpers/explore_helper_spec.rb b/spec/helpers/explore_helper_spec.rb index 5208d3bd656..f8240dd3a4c 100644 --- a/spec/helpers/explore_helper_spec.rb +++ b/spec/helpers/explore_helper_spec.rb @@ -17,4 +17,25 @@ describe ExploreHelper do expect(helper.explore_nav_links).to contain_exactly(*menu_items) end end + + describe '#public_visibility_restricted?' do + using RSpec::Parameterized::TableSyntax + + where(:visibility_levels, :expected_status) do + nil | nil + [Gitlab::VisibilityLevel::PRIVATE] | false + [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL] | false + [Gitlab::VisibilityLevel::PUBLIC] | true + end + + with_them do + before do + stub_application_setting(restricted_visibility_levels: visibility_levels) + end + + it 'returns the expected status' do + expect(helper.public_visibility_restricted?).to eq(expected_status) + end + end + end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 9881bfd3229..10a424c0c11 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -345,6 +345,7 @@ project: - labels - events - milestones +- sprints - notes - snippets - hooks diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb new file mode 100644 index 00000000000..06e8484a3cb --- /dev/null +++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::Group::TreeSaver do + describe 'saves the group tree into a json object' do + let_it_be(:user) { create(:user) } + let_it_be(:group) { setup_groups } + + let(:shared) { Gitlab::ImportExport::Shared.new(group) } + let(:export_path) { "#{Dir.tmpdir}/group_tree_saver_spec" } + + subject(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) } + + before_all do + group.add_maintainer(user) + end + + before do + allow_next_instance_of(Gitlab::ImportExport) do |import_export| + allow(import_export).to receive(:storage_path).and_return(export_path) + end + end + + after do + FileUtils.rm_rf(export_path) + end + + it 'saves the group successfully' do + expect(group_tree_saver.save).to be true + end + + it 'fails to export a group' do + allow_next_instance_of(Gitlab::ImportExport::JSON::NdjsonWriter) do |ndjson_writer| + allow(ndjson_writer).to receive(:write_relation_array).and_raise(RuntimeError, 'exception') + end + + expect(shared).to receive(:error).with(RuntimeError).and_call_original + + expect(group_tree_saver.save).to be false + end + + context 'exported files' do + before do + group_tree_saver.save + end + + it 'has one group per line' do + groups_catalog = + File.readlines(exported_path_for('_all.ndjson')) + .map { |line| Integer(line) } + + expect(groups_catalog.size).to eq(3) + expect(groups_catalog).to eq([ + group.id, + group.descendants.first.id, + group.descendants.first.descendants.first.id + ]) + end + + it 'has a file per group' do + group.self_and_descendants.pluck(:id).each do |id| + group_attributes_file = exported_path_for("#{id}.json") + + expect(File.exist?(group_attributes_file)).to be(true) + end + end + + context 'group attributes file' do + let(:group_attributes_file) { exported_path_for("#{group.id}.json") } + let(:group_attributes) { ::JSON.parse(File.read(group_attributes_file)) } + + it 'has a file for each group with its attributes' do + expect(group_attributes['description']).to eq(group.description) + expect(group_attributes['parent_id']).to eq(group.parent_id) + end + + shared_examples 'excluded attributes' do + excluded_attributes = %w[ + owner_id + created_at + updated_at + runners_token + runners_token_encrypted + saml_discovery_token + ] + + excluded_attributes.each do |excluded_attribute| + it 'does not contain excluded attribute' do + expect(group_attributes).not_to include(excluded_attribute => group.public_send(excluded_attribute)) + end + end + end + + include_examples 'excluded attributes' + end + + it 'has a file for each group association' do + group.self_and_descendants do |g| + %w[ + badges + boards + epics + labels + members + milestones + ].each do |association| + path = exported_path_for("#{g.id}", "#{association}.ndjson") + expect(File.exist?(path)).to eq(true), "#{path} does not exist" + end + end + end + end + end + + def exported_path_for(*file) + File.join(group_tree_saver.full_path, 'groups', *file) + end + + def setup_groups + root = setup_group + subgroup = setup_group(parent: root) + setup_group(parent: subgroup) + + root + end + + def setup_group(parent: nil) + group = create(:group, description: 'description', parent: parent) + create(:milestone, group: group) + create(:group_badge, group: group) + group_label = create(:group_label, group: group) + board = create(:board, group: group, milestone_id: Milestone::Upcoming.id) + create(:list, board: board, label: group_label) + create(:group_badge, group: group) + create(:label_priority, label: group_label, priority: 1) + + group + end +end diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 8dabe5a756b..50b045c6aad 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -170,6 +170,11 @@ describe Gitlab::PathRegex do expect(described_class::TOP_LEVEL_ROUTES) .to contain_exactly(*top_level_words), failure_block end + + # We ban new items in this list, see https://gitlab.com/gitlab-org/gitlab/-/issues/215362 + it 'does not allow expansion' do + expect(described_class::TOP_LEVEL_ROUTES.size).to eq(41) + end end describe 'GROUP_ROUTES' do @@ -184,6 +189,11 @@ describe Gitlab::PathRegex do expect(described_class::GROUP_ROUTES) .to contain_exactly(*paths_after_group_id), failure_block end + + # We ban new items in this list, see https://gitlab.com/gitlab-org/gitlab/-/issues/215362 + it 'does not allow expansion' do + expect(described_class::GROUP_ROUTES.size).to eq(1) + end end describe 'PROJECT_WILDCARD_ROUTES' do @@ -195,6 +205,11 @@ describe Gitlab::PathRegex do end end end + + # We ban new items in this list, see https://gitlab.com/gitlab-org/gitlab/-/issues/215362 + it 'does not allow expansion' do + expect(described_class::PROJECT_WILDCARD_ROUTES.size).to eq(21) + end end describe '.root_namespace_route_regex' do diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb index 56c7121cc34..f77b5a2e5b9 100644 --- a/spec/services/groups/import_export/export_service_spec.rb +++ b/spec/services/groups/import_export/export_service_spec.rb @@ -11,7 +11,7 @@ describe Groups::ImportExport::ExportService do let(:export_service) { described_class.new(group: group, user: user) } it 'enqueues an export job' do - expect(GroupExportWorker).to receive(:perform_async).with(user.id, group.id, {}) + allow(GroupExportWorker).to receive(:perform_async).with(user.id, group.id, {}) export_service.async_execute end @@ -49,7 +49,17 @@ describe Groups::ImportExport::ExportService do FileUtils.rm_rf(archive_path) end - it 'saves the models' do + it 'saves the models using ndjson tree saver' do + stub_feature_flags(group_import_export_ndjson: true) + + expect(Gitlab::ImportExport::Group::TreeSaver).to receive(:new).and_call_original + + service.execute + end + + it 'saves the models using legacy tree saver' do + stub_feature_flags(group_import_export_ndjson: false) + expect(Gitlab::ImportExport::Group::LegacyTreeSaver).to receive(:new).and_call_original service.execute diff --git a/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb b/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb index 995cb66a849..76339837351 100644 --- a/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb @@ -76,7 +76,7 @@ RSpec.shared_examples 'a blob replicator' do expect(service).to receive(:execute) expect(::Geo::BlobDownloadService).to receive(:new).with(replicator: replicator).and_return(service) - replicator.consume_created_event + replicator.consume_event_created end end |