diff options
Diffstat (limited to 'spec')
16 files changed, 451 insertions, 69 deletions
diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb index afb792c2ab9..67f6d8ebe32 100644 --- a/spec/features/merge_request/user_creates_merge_request_spec.rb +++ b/spec/features/merge_request/user_creates_merge_request_spec.rb @@ -25,6 +25,11 @@ describe "User creates a merge request", :js do click_button("Compare branches") + page.within('.merge-request-form') do + expect(page.find('#merge_request_title')['placeholder']).to eq 'Title' + expect(page.find('#merge_request_description')['placeholder']).to eq 'Describe the goal of the changes and what reviewers should be aware of.' + end + fill_in("Title", with: title) click_button("Submit merge request") diff --git a/spec/finders/prometheus_metrics_finder_spec.rb b/spec/finders/prometheus_metrics_finder_spec.rb new file mode 100644 index 00000000000..41b2e700e1e --- /dev/null +++ b/spec/finders/prometheus_metrics_finder_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PrometheusMetricsFinder do + describe '#execute' do + let(:finder) { described_class.new(params) } + let(:params) { {} } + + subject { finder.execute } + + context 'with params' do + let_it_be(:project) { create(:project) } + let_it_be(:project_metric) { create(:prometheus_metric, project: project) } + let_it_be(:common_metric) { create(:prometheus_metric, :common) } + let_it_be(:unique_metric) do + create( + :prometheus_metric, + :common, + title: 'Unique title', + y_label: 'Unique y_label', + group: :kubernetes, + identifier: 'identifier', + created_at: 5.minutes.ago + ) + end + + context 'with appropriate indexes' do + before do + allow_any_instance_of(described_class).to receive(:appropriate_index?).and_return(true) + end + + context 'with project' do + let(:params) { { project: project } } + + it { is_expected.to eq([project_metric]) } + end + + context 'with group' do + let(:params) { { group: project_metric.group } } + + it { is_expected.to contain_exactly(common_metric, project_metric) } + end + + context 'with title' do + let(:params) { { title: project_metric.title } } + + it { is_expected.to contain_exactly(project_metric, common_metric) } + end + + context 'with y_label' do + let(:params) { { y_label: project_metric.y_label } } + + it { is_expected.to contain_exactly(project_metric, common_metric) } + end + + context 'with common' do + let(:params) { { common: true } } + + it { is_expected.to contain_exactly(common_metric, unique_metric) } + end + + context 'with ordered' do + let(:params) { { ordered: true } } + + it { is_expected.to eq([unique_metric, project_metric, common_metric]) } + end + + context 'with indentifier' do + let(:params) { { identifier: unique_metric.identifier } } + + it 'raises an error' do + expect { subject }.to raise_error( + ArgumentError, + ':identifier must be scoped to a :project or :common' + ) + end + + context 'with common' do + let(:params) { { identifier: unique_metric.identifier, common: true } } + + it { is_expected.to contain_exactly(unique_metric) } + end + + context 'with id' do + let(:params) { { id: 14, identifier: 'string' } } + + it 'raises an error' do + expect { subject }.to raise_error( + ArgumentError, + 'Only one of :identifier, :id is permitted' + ) + end + end + end + + context 'with id' do + let(:params) { { id: common_metric.id } } + + it { is_expected.to contain_exactly(common_metric) } + end + + context 'with multiple params' do + let(:params) do + { + group: project_metric.group, + title: project_metric.title, + y_label: project_metric.y_label, + common: true, + ordered: true + } + end + + it { is_expected.to contain_exactly(common_metric) } + end + end + + context 'without an appropriate index' do + let(:params) do + { + title: project_metric.title, + ordered: true + } + end + + it 'raises an error' do + expect { subject }.to raise_error( + ArgumentError, + 'An index should exist for params: [:title]' + ) + end + end + end + + context 'without params' do + it 'raises an error' do + expect { subject }.to raise_error( + ArgumentError, + 'Please provide one or more of: [:project, :group, :title, :y_label, :identifier, :id, :common, :ordered]' + ) + end + end + end +end diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js index c36b603b251..0798ca580e2 100644 --- a/spec/frontend/helpers/monitor_helper_spec.js +++ b/spec/frontend/helpers/monitor_helper_spec.js @@ -81,6 +81,17 @@ describe('monitor helper', () => { expect(result.name).toEqual('brpop, brpop'); }); + it('supports hyphenated template variables', () => { + const config = { ...defaultConfig, name: 'expired - {{ test-attribute }}' }; + + const [result] = monitorHelper.makeDataSeries( + [{ metric: { 'test-attribute': 'test-attribute-value' }, values: series }], + config, + ); + + expect(result.name).toEqual('expired - test-attribute-value'); + }); + it('updates multiple series names from templates', () => { const config = { ...defaultConfig, diff --git a/spec/frontend/notes/mock_data.js b/spec/frontend/notes/mock_data.js index dc914ce8355..01cb70d395c 100644 --- a/spec/frontend/notes/mock_data.js +++ b/spec/frontend/notes/mock_data.js @@ -1094,8 +1094,9 @@ export const collapsedSystemNotes = [ noteable_type: 'Issue', resolvable: false, noteable_iid: 12, + start_description_version_id: undefined, note: 'changed the description', - note_html: ' <p dir="auto">changed the description 2 times within 1 minute </p>', + note_html: '<p dir="auto">changed the description</p>', current_user: { can_edit: false, can_award_emoji: true }, resolved: false, resolved_by: null, @@ -1106,7 +1107,6 @@ export const collapsedSystemNotes = [ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_905&user_id=1', human_access: 'Owner', path: '/gitlab-org/gitlab-shell/notes/905', - times_updated: 2, }, ], individual_note: true, diff --git a/spec/frontend/registry/components/collapsible_container_spec.js b/spec/frontend/registry/components/collapsible_container_spec.js index f93ebab1a4d..d035055afd3 100644 --- a/spec/frontend/registry/components/collapsible_container_spec.js +++ b/spec/frontend/registry/components/collapsible_container_spec.js @@ -1,10 +1,11 @@ import Vue from 'vue'; import Vuex from 'vuex'; import { mount, createLocalVue } from '@vue/test-utils'; -import collapsibleComponent from '~/registry/components/collapsible_container.vue'; -import { repoPropsData } from '../mock_data'; import createFlash from '~/flash'; +import Tracking from '~/tracking'; +import collapsibleComponent from '~/registry/components/collapsible_container.vue'; import * as getters from '~/registry/stores/getters'; +import { repoPropsData } from '../mock_data'; jest.mock('~/flash.js'); @@ -16,9 +17,10 @@ describe('collapsible registry container', () => { let wrapper; let store; - const findDeleteBtn = w => w.find('.js-remove-repo'); - const findContainerImageTags = w => w.find('.container-image-tags'); - const findToggleRepos = w => w.findAll('.js-toggle-repo'); + const findDeleteBtn = (w = wrapper) => w.find('.js-remove-repo'); + const findContainerImageTags = (w = wrapper) => w.find('.container-image-tags'); + const findToggleRepos = (w = wrapper) => w.findAll('.js-toggle-repo'); + const findDeleteModal = (w = wrapper) => w.find({ ref: 'deleteModal' }); const mountWithStore = config => mount(collapsibleComponent, { ...config, store, localVue }); @@ -124,4 +126,45 @@ describe('collapsible registry container', () => { expect(deleteBtn.exists()).toBe(false); }); }); + + describe('tracking', () => { + const category = 'mock_page'; + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + wrapper.vm.deleteItem = jest.fn().mockResolvedValue(); + wrapper.vm.fetchRepos = jest.fn(); + wrapper.setData({ + tracking: { + ...wrapper.vm.tracking, + category, + }, + }); + }); + + it('send an event when delete button is clicked', () => { + const deleteBtn = findDeleteBtn(); + deleteBtn.trigger('click'); + expect(Tracking.event).toHaveBeenCalledWith(category, 'click_button', { + label: 'registry_repository_delete', + category, + }); + }); + it('send an event when cancel is pressed on modal', () => { + const deleteModal = findDeleteModal(); + deleteModal.vm.$emit('cancel'); + expect(Tracking.event).toHaveBeenCalledWith(category, 'cancel_delete', { + label: 'registry_repository_delete', + category, + }); + }); + it('send an event when confirm is clicked on modal', () => { + const deleteModal = findDeleteModal(); + deleteModal.vm.$emit('ok'); + + expect(Tracking.event).toHaveBeenCalledWith(category, 'confirm_delete', { + label: 'registry_repository_delete', + category, + }); + }); + }); }); diff --git a/spec/frontend/registry/components/table_registry_spec.js b/spec/frontend/registry/components/table_registry_spec.js index 7cb7c012d9d..ab88caf44e1 100644 --- a/spec/frontend/registry/components/table_registry_spec.js +++ b/spec/frontend/registry/components/table_registry_spec.js @@ -1,10 +1,14 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import tableRegistry from '~/registry/components/table_registry.vue'; import { mount, createLocalVue } from '@vue/test-utils'; +import createFlash from '~/flash'; +import Tracking from '~/tracking'; +import tableRegistry from '~/registry/components/table_registry.vue'; import { repoPropsData } from '../mock_data'; import * as getters from '~/registry/stores/getters'; +jest.mock('~/flash'); + const [firstImage, secondImage] = repoPropsData.list; const localVue = createLocalVue(); @@ -15,11 +19,12 @@ describe('table registry', () => { let wrapper; let store; - const findSelectAllCheckbox = w => w.find('.js-select-all-checkbox > input'); - const findSelectCheckboxes = w => w.findAll('.js-select-checkbox > input'); - const findDeleteButton = w => w.find('.js-delete-registry'); - const findDeleteButtonsRow = w => w.findAll('.js-delete-registry-row'); - const findPagination = w => w.find('.js-registry-pagination'); + const findSelectAllCheckbox = (w = wrapper) => w.find('.js-select-all-checkbox > input'); + const findSelectCheckboxes = (w = wrapper) => w.findAll('.js-select-checkbox > input'); + const findDeleteButton = (w = wrapper) => w.find({ ref: 'bulkDeleteButton' }); + const findDeleteButtonsRow = (w = wrapper) => w.findAll('.js-delete-registry-row'); + const findPagination = (w = wrapper) => w.find('.js-registry-pagination'); + const findDeleteModal = (w = wrapper) => w.find({ ref: 'deleteModal' }); const bulkDeletePath = 'path'; const mountWithStore = config => mount(tableRegistry, { ...config, store, localVue }); @@ -139,7 +144,7 @@ describe('table registry', () => { }, }); wrapper.vm.handleMultipleDelete(); - expect(wrapper.vm.showError).toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalled(); }); }); @@ -169,6 +174,27 @@ describe('table registry', () => { }); }); + describe('modal event handlers', () => { + beforeEach(() => { + wrapper.vm.handleSingleDelete = jest.fn(); + wrapper.vm.handleMultipleDelete = jest.fn(); + }); + it('on ok when one item is selected should call singleDelete', () => { + wrapper.setData({ itemsToBeDeleted: [0] }); + wrapper.vm.onDeletionConfirmed(); + + expect(wrapper.vm.handleSingleDelete).toHaveBeenCalledWith(repoPropsData.list[0]); + expect(wrapper.vm.handleMultipleDelete).not.toHaveBeenCalled(); + }); + it('on ok when multiple items are selected should call muultiDelete', () => { + wrapper.setData({ itemsToBeDeleted: [0, 1, 2] }); + wrapper.vm.onDeletionConfirmed(); + + expect(wrapper.vm.handleMultipleDelete).toHaveBeenCalled(); + expect(wrapper.vm.handleSingleDelete).not.toHaveBeenCalled(); + }); + }); + describe('pagination', () => { const repo = { repoPropsData, @@ -265,4 +291,83 @@ describe('table registry', () => { expect(deleteBtns.length).toBe(0); }); }); + + describe('event tracking', () => { + const mockPageName = 'mock_page'; + + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + wrapper.vm.handleSingleDelete = jest.fn(); + wrapper.vm.handleMultipleDelete = jest.fn(); + document.body.dataset.page = mockPageName; + }); + + afterEach(() => { + document.body.dataset.page = null; + }); + + describe('single tag delete', () => { + beforeEach(() => { + wrapper.setData({ itemsToBeDeleted: [0] }); + }); + + it('send an event when delete button is clicked', () => { + const deleteBtn = findDeleteButtonsRow(); + deleteBtn.at(0).trigger('click'); + expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'click_button', { + label: 'registry_tag_delete', + property: 'foo', + }); + }); + it('send an event when cancel is pressed on modal', () => { + const deleteModal = findDeleteModal(); + deleteModal.vm.$emit('cancel'); + expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'cancel_delete', { + label: 'registry_tag_delete', + property: 'foo', + }); + }); + it('send an event when confirm is clicked on modal', () => { + const deleteModal = findDeleteModal(); + deleteModal.vm.$emit('ok'); + + expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'confirm_delete', { + label: 'registry_tag_delete', + property: 'foo', + }); + }); + }); + describe('bulk tag delete', () => { + beforeEach(() => { + const items = [0, 1, 2]; + wrapper.setData({ itemsToBeDeleted: items, selectedItems: items }); + }); + + it('send an event when delete button is clicked', () => { + const deleteBtn = findDeleteButton(); + deleteBtn.vm.$emit('click'); + expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'click_button', { + label: 'bulk_registry_tag_delete', + property: 'foo', + }); + }); + it('send an event when cancel is pressed on modal', () => { + const deleteModal = findDeleteModal(); + deleteModal.vm.$emit('cancel'); + expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'cancel_delete', { + label: 'bulk_registry_tag_delete', + property: 'foo', + }); + }); + it('send an event when confirm is clicked on modal', () => { + const deleteModal = findDeleteModal(); + deleteModal.vm.$emit('ok'); + + expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'confirm_delete', { + label: 'bulk_registry_tag_delete', + property: 'foo', + }); + }); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js index a65e3eb294a..c2e8359f78d 100644 --- a/spec/frontend/vue_shared/components/notes/system_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js @@ -57,7 +57,7 @@ describe('system note component', () => { // we need to strip them because they break layout of commit lists in system notes: // https://gitlab.com/gitlab-org/gitlab-foss/uploads/b07a10670919254f0220d3ff5c1aa110/jqzI.png it('removes wrapping paragraph from note HTML', () => { - expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('<span>closed</span>'); + expect(vm.$el.querySelector('.system-note-message').innerHTML).toContain('<span>closed</span>'); }); it('should initMRPopovers onMount', () => { diff --git a/spec/javascripts/bootstrap_jquery_spec.js b/spec/javascripts/bootstrap_jquery_spec.js index 35340a3bc42..6957cf40301 100644 --- a/spec/javascripts/bootstrap_jquery_spec.js +++ b/spec/javascripts/bootstrap_jquery_spec.js @@ -1,5 +1,3 @@ -/* eslint-disable no-var */ - import $ from 'jquery'; import '~/commons/bootstrap'; @@ -10,15 +8,13 @@ describe('Bootstrap jQuery extensions', function() { }); it('adds the disabled attribute', function() { - var $input; - $input = $('input').first(); + const $input = $('input').first(); $input.disable(); expect($input).toHaveAttr('disabled', 'disabled'); }); return it('adds the disabled class', function() { - var $input; - $input = $('input').first(); + const $input = $('input').first(); $input.disable(); expect($input).toHaveClass('disabled'); @@ -30,15 +26,13 @@ describe('Bootstrap jQuery extensions', function() { }); it('removes the disabled attribute', function() { - var $input; - $input = $('input').first(); + const $input = $('input').first(); $input.enable(); expect($input).not.toHaveAttr('disabled'); }); return it('removes the disabled class', function() { - var $input; - $input = $('input').first(); + const $input = $('input').first(); $input.enable(); expect($input).not.toHaveClass('disabled'); diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js index 674db7c7afb..0f20171726c 100644 --- a/spec/javascripts/monitoring/components/dashboard_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_spec.js @@ -122,6 +122,32 @@ describe('Dashboard', () => { }); }); + describe('cluster health', () => { + let wrapper; + + beforeEach(done => { + wrapper = shallowMount(DashboardComponent, { + localVue, + sync: false, + propsData: { ...propsData, hasMetrics: true }, + store, + }); + + // 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(() => { diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js index 498c1a9e9b1..91948b83eec 100644 --- a/spec/javascripts/monitoring/store/mutations_spec.js +++ b/spec/javascripts/monitoring/store/mutations_spec.js @@ -144,7 +144,19 @@ describe('Monitoring mutations', () => { }); describe('SET_ALL_DASHBOARDS', () => { - it('stores the dashboards loaded from the git repository', () => { + it('stores `undefined` dashboards as an empty array', () => { + mutations[types.SET_ALL_DASHBOARDS](stateCopy, undefined); + + expect(stateCopy.allDashboards).toEqual([]); + }); + + it('stores `null` dashboards as an empty array', () => { + mutations[types.SET_ALL_DASHBOARDS](stateCopy, null); + + expect(stateCopy.allDashboards).toEqual([]); + }); + + it('stores dashboards loaded from the git repository', () => { mutations[types.SET_ALL_DASHBOARDS](stateCopy, dashboardGitResponse); expect(stateCopy.allDashboards).toEqual(dashboardGitResponse); diff --git a/spec/javascripts/notes/stores/collapse_utils_spec.js b/spec/javascripts/notes/stores/collapse_utils_spec.js index 8ede9319088..d3019f4b9a4 100644 --- a/spec/javascripts/notes/stores/collapse_utils_spec.js +++ b/spec/javascripts/notes/stores/collapse_utils_spec.js @@ -1,6 +1,5 @@ import { isDescriptionSystemNote, - changeDescriptionNote, getTimeDifferenceMinutes, collapseSystemNotes, } from '~/notes/stores/collapse_utils'; @@ -24,15 +23,6 @@ describe('Collapse utils', () => { ); }); - it('changes the description to contain the number of changed times', () => { - const changedNote = changeDescriptionNote(mockSystemNote, 3, 5); - - expect(changedNote.times_updated).toEqual(3); - expect(changedNote.note_html.trim()).toContain( - '<p dir="auto">changed the description 3 times within 5 minutes </p>', - ); - }); - it('gets the time difference between two notes', () => { const anotherSystemNote = { created_at: '2018-05-14T21:33:00.000Z', diff --git a/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb new file mode 100644 index 00000000000..1fda84f777e --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Connections::FilterableArrayConnection do + let(:callback) { proc { |nodes| nodes } } + let(:all_nodes) { Gitlab::Graphql::FilterableArray.new(callback, 1, 2, 3, 4, 5) } + let(:arguments) { {} } + subject(:connection) do + described_class.new(all_nodes, arguments, max_page_size: 3) + end + + describe '#paged_nodes' do + let(:paged_nodes) { subject.paged_nodes } + + it_behaves_like "connection with paged nodes" + + context 'when callback filters some nodes' do + let(:callback) { proc { |nodes| nodes[1..-1] } } + + it 'does not return filtered elements' do + expect(subject.paged_nodes).to contain_exactly(all_nodes[1], all_nodes[2]) + end + end + end +end diff --git a/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb index ba1addadb5a..9dda2a41ec6 100644 --- a/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb +++ b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb @@ -240,38 +240,16 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do end describe '#paged_nodes' do - let!(:projects) { create_list(:project, 5) } + let_it_be(:all_nodes) { create_list(:project, 5) } + let(:paged_nodes) { subject.paged_nodes } - it 'returns the collection limited to max page size' do - expect(subject.paged_nodes.size).to eq(3) - end - - it 'is a loaded memoized array' do - expect(subject.paged_nodes).to be_an(Array) - expect(subject.paged_nodes.object_id).to eq(subject.paged_nodes.object_id) - end - - context 'when `first` is passed' do - let(:arguments) { { first: 2 } } - - it 'returns only the first elements' do - expect(subject.paged_nodes).to contain_exactly(projects.first, projects.second) - end - end - - context 'when `last` is passed' do - let(:arguments) { { last: 2 } } - - it 'returns only the last elements' do - expect(subject.paged_nodes).to contain_exactly(projects[3], projects[4]) - end - end + it_behaves_like "connection with paged nodes" context 'when both are passed' do let(:arguments) { { first: 2, last: 2 } } it 'raises an error' do - expect { subject.paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + expect { paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) end end diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb index 7f6283715f2..6361893c53c 100644 --- a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb @@ -13,14 +13,19 @@ describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do context 'verify queries' do before do - allow(PrometheusMetric).to receive(:find_by_identifier).and_return(create(:prometheus_metric, query: prometheus_istio_query('test-name', 'test-ns'))) - allow(client).to receive(:query_range) + create(:prometheus_metric, + :common, + identifier: :system_metrics_knative_function_invocation_count, + query: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_app=~"%{function_name}.*"}[1m])*60))') end it 'has the query, but no data' do - results = subject.query(serverless_func.id) + expect(client).to receive(:query_range).with( + 'sum(ceil(rate(istio_requests_total{destination_service_namespace="test-ns", destination_app=~"test-name.*"}[1m])*60))', + hash_including(:start, :stop) + ) - expect(results.queries[0][:query_range]).to eql('floor(sum(rate(istio_revision_request_count{destination_configuration="test-name", destination_namespace="test-ns"}[1m])*30))') + subject.query(serverless_func.id) end end end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 6fb1d279456..80a3f7df05f 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -37,9 +37,12 @@ module GraphqlHelpers # BatchLoader::GraphQL returns a wrapper, so we need to :sync in order # to get the actual values def batch_sync(max_queries: nil, &blk) - result = batch(max_queries: nil, &blk) + wrapper = proc do + lazy_vals = yield + lazy_vals.is_a?(Array) ? lazy_vals.map(&:sync) : lazy_vals&.sync + end - result.is_a?(Array) ? result.map(&:sync) : result&.sync + batch(max_queries: max_queries, &wrapper) end def graphql_query_for(name, attributes = {}, fields = nil) @@ -157,7 +160,13 @@ module GraphqlHelpers def attributes_to_graphql(attributes) attributes.map do |name, value| - "#{GraphqlHelpers.fieldnamerize(name.to_s)}: \"#{value}\"" + value_str = if value.is_a?(Array) + '["' + value.join('","') + '"]' + else + "\"#{value}\"" + end + + "#{GraphqlHelpers.fieldnamerize(name.to_s)}: #{value_str}" end.join(", ") end @@ -282,6 +291,12 @@ module GraphqlHelpers def allow_high_graphql_recursion allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::RecursionAnalyzer).to receive(:recursion_threshold).and_return 1000 end + + def node_array(data, extract_attribute = nil) + data.map do |item| + extract_attribute ? item['node'][extract_attribute] : item['node'] + end + end end # This warms our schema, doing this as part of loading the helpers to avoid diff --git a/spec/support/shared_examples/graphql/connection_paged_nodes.rb b/spec/support/shared_examples/graphql/connection_paged_nodes.rb new file mode 100644 index 00000000000..830d2d2d4b1 --- /dev/null +++ b/spec/support/shared_examples/graphql/connection_paged_nodes.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'connection with paged nodes' do + it 'returns the collection limited to max page size' do + expect(paged_nodes.size).to eq(3) + end + + it 'is a loaded memoized array' do + expect(paged_nodes).to be_an(Array) + expect(paged_nodes.object_id).to eq(paged_nodes.object_id) + end + + context 'when `first` is passed' do + let(:arguments) { { first: 2 } } + + it 'returns only the first elements' do + expect(paged_nodes).to contain_exactly(all_nodes.first, all_nodes.second) + end + end + + context 'when `last` is passed' do + let(:arguments) { { last: 2 } } + + it 'returns only the last elements' do + expect(paged_nodes).to contain_exactly(all_nodes[3], all_nodes[4]) + end + end +end |