diff options
Diffstat (limited to 'spec')
7 files changed, 335 insertions, 176 deletions
diff --git a/spec/features/projects/ci/editor_spec.rb b/spec/features/projects/ci/editor_spec.rb index 43da57c16d1..b09aa91f4ab 100644 --- a/spec/features/projects/ci/editor_spec.rb +++ b/spec/features/projects/ci/editor_spec.rb @@ -11,6 +11,7 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d let(:default_branch) { 'main' } let(:other_branch) { 'test' } let(:branch_with_invalid_ci) { 'despair' } + let(:branch_without_ci) { 'empty' } let(:default_content) { 'Default' } @@ -45,6 +46,7 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d project.repository.create_file(user, project.ci_config_path_or_default, default_content, message: 'Create CI file for main', branch_name: default_branch) project.repository.create_file(user, project.ci_config_path_or_default, valid_content, message: 'Create CI file for test', branch_name: other_branch) project.repository.create_file(user, project.ci_config_path_or_default, invalid_content, message: 'Create CI file for test', branch_name: branch_with_invalid_ci) + project.repository.create_file(user, 'index.js', "file", message: 'New js file', branch_name: branch_without_ci) visit project_ci_pipeline_editor_path(project) wait_for_requests @@ -62,6 +64,31 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d end end + describe 'when there are no CI config file' do + before do + visit project_ci_pipeline_editor_path(project, branch_name: branch_without_ci) + end + + it 'renders the empty page', :aggregate_failures do + expect(page).to have_content 'Optimize your workflow with CI/CD Pipelines' + expect(page).to have_selector '[data-testid="create_new_ci_button"]' + end + + context 'when clicking on the create new CI button' do + before do + click_button 'Configure pipeline' + end + + it 'renders the source editor with default content', :aggregate_failures do + expect(page).to have_selector('#source-editor-') + + page.within('#source-editor-') do + expect(page).to have_content('This file is a template, and might need editing before it works on your project.') + end + end + end + end + describe 'When CI yml has valid syntax' do before do visit project_ci_pipeline_editor_path(project, branch_name: other_branch) @@ -149,15 +176,6 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d end shared_examples 'default branch switcher behavior' do - def switch_to_branch(branch) - find('[data-testid="branch-selector"]').click - - page.within '[data-testid="branch-selector"]' do - click_button branch - wait_for_requests - end - end - it 'displays current branch' do page.within('[data-testid="branch-selector"]') do expect(page).to have_content(default_branch) @@ -195,12 +213,20 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d end describe 'Branch Switcher' do + def switch_to_branch(branch) + # close button for the popover + find('[data-testid="close-button"]').click + find('[data-testid="branch-selector"]').click + + page.within '[data-testid="branch-selector"]' do + click_button branch + wait_for_requests + end + end + before do visit project_ci_pipeline_editor_path(project) wait_for_requests - - # close button for the popover - find('[data-testid="close-button"]').click end it_behaves_like 'default branch switcher behavior' @@ -262,6 +288,24 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d end describe 'Commit Form' do + context 'when targetting the main branch' do + it 'does not show the option to create a Merge request', :aggregate_failures do + expect(page).not_to have_selector('[data-testid="new-mr-checkbox"]') + expect(page).not_to have_content('Start a new merge request with these changes') + end + end + + context 'when targetting any non-main branch' do + before do + find('#source-branch-field').set('new_branch', clear: :backspace) + end + + it 'shows the option to create a Merge request', :aggregate_failures do + expect(page).to have_selector('[data-testid="new-mr-checkbox"]') + expect(page).to have_content('Start a new merge request with these changes') + end + end + it 'is preserved when changing tabs' do find('#commit-message').set('message', clear: :backspace) find('#source-branch-field').set('new_branch', clear: :backspace) diff --git a/spec/frontend/diffs/utils/tree_worker_utils_spec.js b/spec/frontend/diffs/utils/tree_worker_utils_spec.js index 5fba00f9258..b29275f45a6 100644 --- a/spec/frontend/diffs/utils/tree_worker_utils_spec.js +++ b/spec/frontend/diffs/utils/tree_worker_utils_spec.js @@ -382,7 +382,7 @@ describe('~/diffs/utils/tree_worker_utils', () => { }, { type: 'tree', - name: 'ee/lib/…/…/…/longtreenametomakepath', + name: 'ee/lib/ee/gitlab/checks/longtreenametomakepath', tree: [ { name: 'diff_check.rb', diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 8f1f6899935..b7d6bbd3991 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -238,36 +238,6 @@ describe('text_utility', () => { }); }); - describe('truncatePathMiddleToLength', () => { - it('does not truncate text', () => { - expect(textUtils.truncatePathMiddleToLength('app/test', 50)).toEqual('app/test'); - }); - - it('truncates middle of the path', () => { - expect(textUtils.truncatePathMiddleToLength('app/test/diff', 13)).toEqual('app/…/diff'); - }); - - it('truncates multiple times in the middle of the path', () => { - expect(textUtils.truncatePathMiddleToLength('app/test/merge_request/diff', 13)).toEqual( - 'app/…/…/diff', - ); - }); - - describe('given a path too long for the maxWidth', () => { - it.each` - path | maxWidth | result - ${'aa/bb/cc'} | ${1} | ${'…'} - ${'aa/bb/cc'} | ${2} | ${'…'} - ${'aa/bb/cc'} | ${3} | ${'…/…'} - ${'aa/bb/cc'} | ${4} | ${'…/…'} - ${'aa/bb/cc'} | ${5} | ${'…/…/…'} - `('truncates ($path, $maxWidth) to $result', ({ path, maxWidth, result }) => { - expect(result.length).toBeLessThanOrEqual(maxWidth); - expect(textUtils.truncatePathMiddleToLength(path, maxWidth)).toEqual(result); - }); - }); - }); - describe('slugifyWithUnderscore', () => { it('should replaces whitespaces with underscore and convert to lower case', () => { expect(textUtils.slugifyWithUnderscore('My Input String')).toEqual('my_input_string'); diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js index 9343a3a5e90..18fdba32f52 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js @@ -121,14 +121,15 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { }); describe('fetch', () => { - it('sets the data.collapsed property after a successfull call - multiPolling: false', async () => { + it('calls fetchCollapsedData properly when multiPolling is false', async () => { const mockData = { headers: {}, status: HTTP_STATUS_OK, data: { vulnerabilities: [] } }; - createComponent({ propsData: { fetchCollapsedData: () => Promise.resolve(mockData) } }); + const fetchCollapsedData = jest.fn().mockResolvedValue(mockData); + createComponent({ propsData: { fetchCollapsedData } }); await waitForPromises(); - expect(wrapper.emitted('input')[0][0]).toEqual({ collapsed: mockData.data, expanded: null }); + expect(fetchCollapsedData).toHaveBeenCalledTimes(1); }); - it('sets the data.collapsed property after a successfull call - multiPolling: true', async () => { + it('calls fetchCollapsedData properly when multiPolling is true', async () => { const mockData1 = { headers: {}, status: HTTP_STATUS_OK, @@ -140,22 +141,22 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { data: { vulnerabilities: [{ vuln: 2 }] }, }; + const fetchCollapsedData = [ + jest.fn().mockResolvedValue(mockData1), + jest.fn().mockResolvedValue(mockData2), + ]; + createComponent({ propsData: { multiPolling: true, - fetchCollapsedData: () => [ - () => Promise.resolve(mockData1), - () => Promise.resolve(mockData2), - ], + fetchCollapsedData: () => fetchCollapsedData, }, }); await waitForPromises(); - expect(wrapper.emitted('input')[0][0]).toEqual({ - collapsed: [mockData1.data, mockData2.data], - expanded: null, - }); + expect(fetchCollapsedData[0]).toHaveBeenCalledTimes(1); + expect(fetchCollapsedData[1]).toHaveBeenCalledTimes(1); }); it('throws an error when the handler does not include headers or status objects', async () => { @@ -328,11 +329,12 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { }; const fetchExpandedData = jest.fn().mockResolvedValue(mockDataExpanded); + const fetchCollapsedData = jest.fn().mockResolvedValue(mockDataCollapsed); await createComponent({ propsData: { isCollapsible: true, - fetchCollapsedData: () => Promise.resolve(mockDataCollapsed), + fetchCollapsedData, fetchExpandedData, }, }); @@ -340,17 +342,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { findToggleButton().vm.$emit('click'); await waitForPromises(); - // First fetches the collapsed data - expect(wrapper.emitted('input')[0][0]).toEqual({ - collapsed: mockDataCollapsed.data, - expanded: null, - }); - - // Then fetches the expanded data - expect(wrapper.emitted('input')[1][0]).toEqual({ - collapsed: null, - expanded: mockDataExpanded.data, - }); + expect(fetchCollapsedData).toHaveBeenCalledTimes(1); + expect(fetchExpandedData).toHaveBeenCalledTimes(1); // Triggering a click does not call the expanded data again findToggleButton().vm.$emit('click'); @@ -371,14 +364,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { findToggleButton().vm.$emit('click'); await waitForPromises(); - // First fetches the collapsed data - expect(wrapper.emitted('input')[0][0]).toEqual({ - collapsed: undefined, - expanded: null, - }); - expect(fetchExpandedData).toHaveBeenCalledTimes(1); - expect(wrapper.emitted('input')).toHaveLength(1); // Should not an emit an input call because request failed findToggleButton().vm.$emit('click'); await waitForPromises(); diff --git a/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb b/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb index b744adc570f..1621ff6655d 100644 --- a/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb +++ b/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb @@ -9,151 +9,185 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli subject { described_class.new(result, arguments) } - context 'when input data is valid' do - let(:header) do - { spec: { inputs: { website: nil } } } - end + # Remove shared examples when ci_interpolation_inputs_refactor is removed. + shared_examples 'interpolator' do + context 'when input data is valid' do + let(:header) do + { spec: { inputs: { website: nil } } } + end - let(:content) do - { test: 'deploy $[[ inputs.website ]]' } - end + let(:content) do + { test: 'deploy $[[ inputs.website ]]' } + end - let(:arguments) do - { website: 'gitlab.com' } - end + let(:arguments) do + { website: 'gitlab.com' } + end - it 'correctly interpolates the config' do - subject.interpolate! + it 'correctly interpolates the config' do + subject.interpolate! - expect(subject).to be_valid - expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' }) + expect(subject).to be_interpolated + expect(subject).to be_valid + expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' }) + end end - end - context 'when config has a syntax error' do - let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(error: 'Invalid configuration format') } + context 'when config has a syntax error' do + let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(error: 'Invalid configuration format') } - let(:arguments) do - { website: 'gitlab.com' } - end + let(:arguments) do + { website: 'gitlab.com' } + end - it 'surfaces an error about invalid config' do - subject.interpolate! + it 'surfaces an error about invalid config' do + subject.interpolate! - expect(subject).not_to be_valid - expect(subject.error_message).to eq subject.errors.first - expect(subject.errors).to include 'Invalid configuration format' + expect(subject).not_to be_valid + expect(subject.error_message).to eq subject.errors.first + expect(subject.errors).to include 'Invalid configuration format' + end end - end - context 'when spec header is invalid' do - let(:header) do - { spec: { arguments: { website: nil } } } - end + context 'when spec header is invalid' do + let(:header) do + { spec: { arguments: { website: nil } } } + end - let(:content) do - { test: 'deploy $[[ inputs.website ]]' } - end + let(:content) do + { test: 'deploy $[[ inputs.website ]]' } + end - let(:arguments) do - { website: 'gitlab.com' } - end + let(:arguments) do + { website: 'gitlab.com' } + end - it 'surfaces an error about invalid header' do - subject.interpolate! + it 'surfaces an error about invalid header' do + subject.interpolate! - expect(subject).not_to be_valid - expect(subject.error_message).to eq subject.errors.first - expect(subject.errors).to include('header:spec config contains unknown keys: arguments') + expect(subject).not_to be_valid + expect(subject.error_message).to eq subject.errors.first + expect(subject.errors).to include('header:spec config contains unknown keys: arguments') + end end - end - context 'when interpolation block is invalid' do - let(:header) do - { spec: { inputs: { website: nil } } } - end + context 'when interpolation block is invalid' do + let(:header) do + { spec: { inputs: { website: nil } } } + end - let(:content) do - { test: 'deploy $[[ inputs.abc ]]' } - end + let(:content) do + { test: 'deploy $[[ inputs.abc ]]' } + end - let(:arguments) do - { website: 'gitlab.com' } - end + let(:arguments) do + { website: 'gitlab.com' } + end - it 'correctly interpolates the config' do - subject.interpolate! + it 'correctly interpolates the config' do + subject.interpolate! - expect(subject).not_to be_valid - expect(subject.errors).to include 'unknown interpolation key: `abc`' - expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`' + expect(subject).not_to be_valid + expect(subject.errors).to include 'unknown interpolation key: `abc`' + expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`' + end end - end - context 'when provided interpolation argument is invalid' do - let(:header) do - { spec: { inputs: { website: nil } } } - end + context 'when multiple interpolation blocks are invalid' do + let(:header) do + { spec: { inputs: { website: nil } } } + end - let(:content) do - { test: 'deploy $[[ inputs.website ]]' } - end + let(:content) do + { test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]' } + end - let(:arguments) do - { website: ['gitlab.com'] } - end + let(:arguments) do + { website: 'gitlab.com' } + end - it 'correctly interpolates the config' do - subject.interpolate! + it 'correctly interpolates the config' do + subject.interpolate! - expect(subject).not_to be_valid - expect(subject.error_message).to eq subject.errors.first - expect(subject.errors).to include 'unsupported value in input argument `website`' + expect(subject).not_to be_valid + expect(subject.error_message) + .to eq 'interpolation interrupted by errors, unknown interpolation key: `something`' + end end - end - context 'when multiple interpolation blocks are invalid' do - let(:header) do - { spec: { inputs: { website: nil } } } - end + describe '#to_hash' do + context 'when interpolation is not used' do + let(:result) do + ::Gitlab::Ci::Config::Yaml::Result.new(config: content) + end - let(:content) do - { test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]' } - end + let(:content) do + { test: 'deploy production' } + end - let(:arguments) do - { website: 'gitlab.com' } - end + let(:arguments) { nil } + + it 'returns original content' do + subject.interpolate! + + expect(subject.to_hash).to eq(content) + end + end - it 'correctly interpolates the config' do - subject.interpolate! + context 'when interpolation is available' do + let(:header) do + { spec: { inputs: { website: nil } } } + end - expect(subject).not_to be_valid - expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `something`' + let(:content) do + { test: 'deploy $[[ inputs.website ]]' } + end + + let(:arguments) do + { website: 'gitlab.com' } + end + + it 'correctly interpolates content' do + subject.interpolate! + + expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' }) + end + end end end - describe '#to_hash' do - context 'when interpolation is not used' do - let(:result) do - ::Gitlab::Ci::Config::Yaml::Result.new(config: content) + it_behaves_like 'interpolator' do + context 'when provided interpolation argument is invalid' do + let(:header) do + { spec: { inputs: { website: nil } } } end let(:content) do - { test: 'deploy production' } + { test: 'deploy $[[ inputs.website ]]' } end - let(:arguments) { nil } + let(:arguments) do + { website: ['gitlab.com'] } + end - it 'returns original content' do + it 'returns an error' do subject.interpolate! - expect(subject).not_to be_interpolated - expect(subject.to_hash).to eq(content) + expect(subject).not_to be_valid + expect(subject.error_message).to eq subject.errors.first + expect(subject.errors).to include '`website` input: provided value is not a string' end end + end - context 'when interpolation is available' do + context 'when feature flag ci_interpolation_inputs_refactor is disabled' do + before do + stub_feature_flags(ci_interpolation_inputs_refactor: false) + end + + it_behaves_like 'interpolator' + + context 'when provided interpolation argument is invalid' do let(:header) do { spec: { inputs: { website: nil } } } end @@ -163,14 +197,15 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli end let(:arguments) do - { website: 'gitlab.com' } + { website: ['gitlab.com'] } end - it 'correctly interpolates content' do + it 'returns an error' do subject.interpolate! - expect(subject).to be_interpolated - expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' }) + expect(subject).not_to be_valid + expect(subject.error_message).to eq subject.errors.first + expect(subject.errors).to include 'unsupported value in input argument `website`' end end end diff --git a/spec/lib/gitlab/ci/interpolation/inputs/base_input_spec.rb b/spec/lib/gitlab/ci/interpolation/inputs/base_input_spec.rb new file mode 100644 index 00000000000..30dbf1ffe51 --- /dev/null +++ b/spec/lib/gitlab/ci/interpolation/inputs/base_input_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Interpolation::Inputs::BaseInput, feature_category: :pipeline_composition do + describe '.matches?' do + it 'is not implemented' do + expect { described_class.matches?(double) }.to raise_error(NotImplementedError) + end + end + + describe '.type_name' do + it 'is not implemented' do + expect { described_class.type_name }.to raise_error(NotImplementedError) + end + end + + describe '#valid_value?' do + it 'is not implemented' do + expect do + described_class.new( + name: 'website', spec: { website: nil }, value: { website: 'example.com' } + ).valid_value?('test') + end.to raise_error(NotImplementedError) + end + end +end diff --git a/spec/lib/gitlab/ci/interpolation/inputs_spec.rb b/spec/lib/gitlab/ci/interpolation/inputs_spec.rb new file mode 100644 index 00000000000..d7d7c85d04f --- /dev/null +++ b/spec/lib/gitlab/ci/interpolation/inputs_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Interpolation::Inputs, feature_category: :pipeline_composition do + let(:inputs) { described_class.new(specs, args) } + let(:specs) { { foo: { default: 'bar' } } } + let(:args) { {} } + + context 'when inputs are valid' do + where(:specs, :args, :merged) do + [ + [ + { foo: { default: 'bar' } }, {}, + { foo: 'bar' } + ], + [ + { foo: { default: 'bar' } }, { foo: 'test' }, + { foo: 'test' } + ], + [ + { foo: nil }, { foo: 'bar' }, + { foo: 'bar' } + ], + [ + { foo: { type: 'string' } }, { foo: 'bar' }, + { foo: 'bar' } + ], + [ + { foo: { type: 'string', default: 'bar' } }, { foo: 'test' }, + { foo: 'test' } + ], + [ + { foo: { type: 'string', default: 'bar' } }, {}, + { foo: 'bar' } + ], + [ + { foo: { default: 'bar' }, baz: nil }, { baz: 'test' }, + { foo: 'bar', baz: 'test' } + ] + ] + end + + with_them do + it 'contains the merged inputs' do + expect(inputs).to be_valid + expect(inputs.to_hash).to eq(merged) + end + end + end + + context 'when inputs are invalid' do + where(:specs, :args, :errors) do + [ + [ + { foo: nil }, { foo: 'bar', test: 'bar' }, + ['unknown input arguments: test'] + ], + [ + { foo: nil }, { test: 'bar', gitlab: '1' }, + ['unknown input arguments: test, gitlab', '`foo` input: required value has not been provided'] + ], + [ + { foo: 123 }, {}, + ['unknown input specification for `foo` (valid types: string)'] + ], + [ + { a: nil, foo: 123 }, { a: '123' }, + ['unknown input specification for `foo` (valid types: string)'] + ], + [ + { foo: nil }, {}, + ['`foo` input: required value has not been provided'] + ], + [ + { foo: { default: 123 } }, { foo: 'test' }, + ['`foo` input: default value is not a string'] + ], + [ + { foo: { default: 'test' } }, { foo: 123 }, + ['`foo` input: provided value is not a string'] + ], + [ + { foo: nil }, { foo: 123 }, + ['`foo` input: provided value is not a string'] + ] + ] + end + + with_them do + it 'contains the merged inputs', :aggregate_failures do + expect(inputs).not_to be_valid + expect(inputs.errors).to contain_exactly(*errors) + end + end + end +end |