diff options
Diffstat (limited to 'spec')
10 files changed, 367 insertions, 28 deletions
diff --git a/spec/features/admin/integrations/user_activates_mattermost_slash_command_spec.rb b/spec/features/admin/integrations/user_activates_mattermost_slash_command_spec.rb index 6f091d37995..c4626996d0c 100644 --- a/spec/features/admin/integrations/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/admin/integrations/user_activates_mattermost_slash_command_spec.rb @@ -11,6 +11,24 @@ RSpec.describe 'User activates the instance-level Mattermost Slash Command integ end let(:edit_path) { edit_admin_application_settings_integration_path(:mattermost_slash_commands) } + let(:overrides_path) { overrides_admin_application_settings_integration_path(:mattermost_slash_commands) } include_examples 'user activates the Mattermost Slash Command integration' + + it 'displays navigation tabs' do + expect(page).to have_link('Settings', href: edit_path) + expect(page).to have_link('Projects using custom settings', href: overrides_path) + end + + context 'when instance_level_integration_overrides is disabled' do + before do + stub_feature_flags(instance_level_integration_overrides: false) + visit_instance_integration('Mattermost slash commands') + end + + it 'does not display the overrides tab' do + expect(page).not_to have_link('Settings', href: edit_path) + expect(page).not_to have_link('Projects using custom settings', href: overrides_path) + end + end end diff --git a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb index d6cdc15005b..7b7fff5c936 100644 --- a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb +++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb @@ -31,7 +31,7 @@ RSpec.describe 'Merge request > User sees closing issues message', :js do let(:merge_request_description) { "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -39,7 +39,7 @@ RSpec.describe 'Merge request > User sees closing issues message', :js do let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Mentions #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Mentions issues #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -47,8 +47,8 @@ RSpec.describe 'Merge request > User sees closing issues message', :js do let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes #{issue_1.to_reference}") - expect(page).to have_content("Mentions #{issue_2.to_reference}") + expect(page).to have_content("Closes issue #{issue_1.to_reference}") + expect(page).to have_content("Mentions issue #{issue_2.to_reference}") end end @@ -56,7 +56,7 @@ RSpec.describe 'Merge request > User sees closing issues message', :js do let(:merge_request_title) { "closing #{issue_1.to_reference}, #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -64,7 +64,7 @@ RSpec.describe 'Merge request > User sees closing issues message', :js do let(:merge_request_title) { "Refers to #{issue_1.to_reference} and #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Mentions #{issue_1.to_reference} and #{issue_2.to_reference}") + expect(page).to have_content("Mentions issues #{issue_1.to_reference} and #{issue_2.to_reference}") end end @@ -72,8 +72,8 @@ RSpec.describe 'Merge request > User sees closing issues message', :js do let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Closes #{issue_1.to_reference}") - expect(page).to have_content("Mentions #{issue_2.to_reference}") + expect(page).to have_content("Closes issue #{issue_1.to_reference}") + expect(page).to have_content("Mentions issue #{issue_2.to_reference}") end end end diff --git a/spec/frontend/content_editor/components/toolbar_table_button_spec.js b/spec/frontend/content_editor/components/toolbar_table_button_spec.js index 336391f9b63..056e5e04e1f 100644 --- a/spec/frontend/content_editor/components/toolbar_table_button_spec.js +++ b/spec/frontend/content_editor/components/toolbar_table_button_spec.js @@ -29,17 +29,17 @@ describe('content_editor/components/toolbar_table_button', () => { wrapper.destroy(); }); - it('renders a grid of 3x3 buttons to create a table', () => { - expect(getNumButtons()).toBe(9); // 3 x 3 + it('renders a grid of 5x5 buttons to create a table', () => { + expect(getNumButtons()).toBe(25); // 5x5 }); describe.each` row | col | numButtons | tableSize - ${1} | ${2} | ${9} | ${'1x2'} - ${2} | ${2} | ${9} | ${'2x2'} - ${2} | ${3} | ${12} | ${'2x3'} - ${3} | ${2} | ${12} | ${'3x2'} - ${3} | ${3} | ${16} | ${'3x3'} + ${3} | ${4} | ${25} | ${'3x4'} + ${4} | ${4} | ${25} | ${'4x4'} + ${4} | ${5} | ${30} | ${'4x5'} + ${5} | ${4} | ${30} | ${'5x4'} + ${5} | ${5} | ${36} | ${'5x5'} `('button($row, $col) in the table creator grid', ({ row, col, numButtons, tableSize }) => { describe('on mouse over', () => { beforeEach(async () => { @@ -50,9 +50,7 @@ describe('content_editor/components/toolbar_table_button', () => { it('marks all rows and cols before it as active', () => { const prevRow = Math.max(1, row - 1); const prevCol = Math.max(1, col - 1); - expect(wrapper.findByTestId(`table-${prevRow}-${prevCol}`).element).toHaveClass( - 'gl-bg-blue-50!', - ); + expect(wrapper.findByTestId(`table-${prevRow}-${prevCol}`).element).toHaveClass('active'); }); it('shows a help text indicating the size of the table being inserted', () => { @@ -89,8 +87,8 @@ describe('content_editor/components/toolbar_table_button', () => { }); }); - it('does not create more buttons than a 8x8 grid', async () => { - for (let i = 3; i < 8; i += 1) { + it('does not create more buttons than a 10x10 grid', async () => { + for (let i = 5; i < 10; i += 1) { expect(getNumButtons()).toBe(i * i); // eslint-disable-next-line no-await-in-loop @@ -98,6 +96,6 @@ describe('content_editor/components/toolbar_table_button', () => { expect(findDropdown().element).toHaveText(`Insert a ${i}x${i} table.`); } - expect(getNumButtons()).toBe(64); // 8x8 (and not 9x9) + expect(getNumButtons()).toBe(100); // 10x10 (and not 11x11) }); }); diff --git a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js new file mode 100644 index 00000000000..dbed236d7df --- /dev/null +++ b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js @@ -0,0 +1,146 @@ +import { GlTable, GlLink, GlPagination } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import { DEFAULT_PER_PAGE } from '~/api'; +import createFlash from '~/flash'; +import IntegrationOverrides from '~/integrations/overrides/components/integration_overrides.vue'; +import axios from '~/lib/utils/axios_utils'; +import httpStatus from '~/lib/utils/http_status'; +import ProjectAvatar from '~/vue_shared/components/project_avatar.vue'; + +jest.mock('~/flash'); + +const mockOverrides = Array(DEFAULT_PER_PAGE * 3) + .fill(1) + .map((_, index) => ({ + name: `test-proj-${index}`, + avatar_url: `avatar-${index}`, + full_path: `test-proj-${index}`, + full_name: `test-proj-${index}`, + })); + +describe('IntegrationOverrides', () => { + let wrapper; + let mockAxios; + + const defaultProps = { + overridesPath: 'mock/overrides', + }; + + const createComponent = ({ mountFn = shallowMount } = {}) => { + wrapper = mountFn(IntegrationOverrides, { + propsData: defaultProps, + }); + }; + + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, mockOverrides, { + 'X-TOTAL': mockOverrides.length, + 'X-PAGE': 1, + }); + }); + + afterEach(() => { + mockAxios.restore(); + wrapper.destroy(); + }); + + const findGlTable = () => wrapper.findComponent(GlTable); + const findPagination = () => wrapper.findComponent(GlPagination); + const findRowsAsModel = () => + findGlTable() + .findAllComponents(GlLink) + .wrappers.map((link) => { + const avatar = link.findComponent(ProjectAvatar); + + return { + href: link.attributes('href'), + avatarUrl: avatar.props('projectAvatarUrl'), + avatarName: avatar.props('projectName'), + text: link.text(), + }; + }); + + describe('while loading', () => { + it('sets GlTable `busy` attribute to `true`', () => { + createComponent(); + + const table = findGlTable(); + expect(table.exists()).toBe(true); + expect(table.attributes('busy')).toBe('true'); + }); + }); + + describe('when initial request is successful', () => { + it('sets GlTable `busy` attribute to `false`', async () => { + createComponent(); + await waitForPromises(); + + const table = findGlTable(); + expect(table.exists()).toBe(true); + expect(table.attributes('busy')).toBeFalsy(); + }); + + describe('table template', () => { + beforeEach(async () => { + createComponent({ mountFn: mount }); + await waitForPromises(); + }); + + it('renders overrides as rows in table', () => { + expect(findRowsAsModel()).toEqual( + mockOverrides.map((x) => ({ + href: x.full_path, + avatarUrl: x.avatar_url, + avatarName: x.name, + text: expect.stringContaining(x.full_name), + })), + ); + }); + }); + }); + + describe('when request fails', () => { + beforeEach(async () => { + mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.INTERNAL_SERVER_ERROR); + createComponent(); + await waitForPromises(); + }); + + it('calls createFlash', () => { + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith({ + message: IntegrationOverrides.i18n.defaultErrorMessage, + captureError: true, + error: expect.any(Error), + }); + }); + }); + + describe('pagination', () => { + it('triggers fetch when `input` event is emitted', async () => { + createComponent(); + jest.spyOn(axios, 'get'); + await waitForPromises(); + + await findPagination().vm.$emit('input', 2); + expect(axios.get).toHaveBeenCalledWith(defaultProps.overridesPath, { + params: { page: 2, per_page: DEFAULT_PER_PAGE }, + }); + }); + + it('does not render with <=1 page', async () => { + mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], { + 'X-TOTAL': 1, + 'X-PAGE': 1, + }); + + createComponent(); + await waitForPromises(); + + expect(findPagination().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/jobs/components/stages_dropdown_spec.js b/spec/frontend/jobs/components/stages_dropdown_spec.js index b75d1707a8d..b0e95a2d5b6 100644 --- a/spec/frontend/jobs/components/stages_dropdown_spec.js +++ b/spec/frontend/jobs/components/stages_dropdown_spec.js @@ -20,6 +20,7 @@ describe('Stages Dropdown', () => { const findPipelineInfoText = () => wrapper.findByTestId('pipeline-info').text(); const findPipelinePath = () => wrapper.findByTestId('pipeline-path').attributes('href'); const findMRLinkPath = () => wrapper.findByTestId('mr-link').attributes('href'); + const findCopySourceBranchBtn = () => wrapper.findByTestId('copy-source-ref-link'); const findSourceBranchLinkPath = () => wrapper.findByTestId('source-branch-link').attributes('href'); const findTargetBranchLinkPath = () => @@ -70,6 +71,10 @@ describe('Stages Dropdown', () => { expect(actual).toBe(expected); }); + + it(`renders the source ref copy button`, () => { + expect(findCopySourceBranchBtn().exists()).toBe(true); + }); }); describe('with an "attached" merge request pipeline', () => { @@ -103,6 +108,10 @@ describe('Stages Dropdown', () => { mockPipelineWithAttachedMR.merge_request.target_branch_path, ); }); + + it(`renders the source ref copy button`, () => { + expect(findCopySourceBranchBtn().exists()).toBe(true); + }); }); describe('with a detached merge request pipeline', () => { @@ -130,5 +139,9 @@ describe('Stages Dropdown', () => { mockPipelineDetached.merge_request.source_branch_path, ); }); + + it(`renders the source ref copy button`, () => { + expect(findCopySourceBranchBtn().exists()).toBe(true); + }); }); }); diff --git a/spec/frontend/notes/components/comment_field_layout_spec.js b/spec/frontend/notes/components/comment_field_layout_spec.js index 4d9b4ea8c6f..90c989540b9 100644 --- a/spec/frontend/notes/components/comment_field_layout_spec.js +++ b/spec/frontend/notes/components/comment_field_layout_spec.js @@ -134,4 +134,18 @@ describe('Comment Field Layout Component', () => { ]); }); }); + + describe('issue has email participants, but note is confidential', () => { + it('does not show EmailParticipantsWarning', () => { + createWrapper({ + noteableData: { + ...noteableDataMock, + issue_email_participants: [{ email: 'someone@gitlab.com' }], + }, + noteIsConfidential: true, + }); + + expect(findEmailParticipantsWarning().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js index a879b06e858..6ea8ca10c02 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js @@ -17,7 +17,7 @@ describe('MRWidgetRelatedLinks', () => { it('returns Closes text for open merge request', () => { createComponent({ state: 'open', relatedLinks: {} }); - expect(wrapper.vm.closesText).toBe('Closes'); + expect(wrapper.vm.closesText).toBe('Closes issues'); }); it('returns correct text for closed merge request', () => { @@ -38,6 +38,7 @@ describe('MRWidgetRelatedLinks', () => { createComponent({ relatedLinks: { closing: '<a href="#">#23</a> and <a>#42</a>', + closingCount: 2, }, }); const content = wrapper @@ -45,7 +46,7 @@ describe('MRWidgetRelatedLinks', () => { .replace(/\n(\s)+/g, ' ') .trim(); - expect(content).toContain('Closes #23 and #42'); + expect(content).toContain('Closes issues #23 and #42'); expect(content).not.toContain('Mentions'); }); @@ -53,11 +54,17 @@ describe('MRWidgetRelatedLinks', () => { createComponent({ relatedLinks: { mentioned: '<a href="#">#7</a>', + mentionedCount: 1, }, }); - expect(wrapper.text().trim()).toContain('Mentions #7'); - expect(wrapper.text().trim()).not.toContain('Closes'); + const content = wrapper + .text() + .replace(/\n(\s)+/g, ' ') + .trim(); + + expect(content).toContain('Mentions issue #7'); + expect(content).not.toContain('Closes issues'); }); it('should have closing and mentioned issues at the same time', () => { @@ -65,6 +72,8 @@ describe('MRWidgetRelatedLinks', () => { relatedLinks: { closing: '<a href="#">#7</a>', mentioned: '<a href="#">#23</a> and <a>#42</a>', + closingCount: 1, + mentionedCount: 2, }, }); const content = wrapper @@ -72,8 +81,8 @@ describe('MRWidgetRelatedLinks', () => { .replace(/\n(\s)+/g, ' ') .trim(); - expect(content).toContain('Closes #7'); - expect(content).toContain('Mentions #23 and #42'); + expect(content).toContain('Closes issue #7'); + expect(content).toContain('Mentions issues #23 and #42'); }); it('should have assing issues link', () => { diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb index f64afa1ed71..f1e19f17c72 100644 --- a/spec/helpers/clusters_helper_spec.rb +++ b/spec/helpers/clusters_helper_spec.rb @@ -82,6 +82,10 @@ RSpec.describe ClustersHelper do expect(subject[:get_started_docs_url]).to eq(help_page_path('user/clusters/agent/index', anchor: 'define-a-configuration-repository')) expect(subject[:integration_docs_url]).to eq(help_page_path('user/clusters/agent/index', anchor: 'get-started-with-gitops-and-the-gitlab-agent')) end + + it 'displays kas address' do + expect(subject[:kas_address]).to eq(Gitlab::Kas.external_url) + end end describe '#js_clusters_list_data' do diff --git a/spec/migrations/20210805192450_update_trial_plans_ci_daily_pipeline_schedule_triggers_spec.rb b/spec/migrations/20210805192450_update_trial_plans_ci_daily_pipeline_schedule_triggers_spec.rb new file mode 100644 index 00000000000..819120d43ef --- /dev/null +++ b/spec/migrations/20210805192450_update_trial_plans_ci_daily_pipeline_schedule_triggers_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration!('update_trial_plans_ci_daily_pipeline_schedule_triggers') + +RSpec.describe UpdateTrialPlansCiDailyPipelineScheduleTriggers, :migration do + let!(:plans) { table(:plans) } + let!(:plan_limits) { table(:plan_limits) } + let!(:premium_trial_plan) { plans.create!(name: 'premium_trial', title: 'Premium Trial') } + let!(:ultimate_trial_plan) { plans.create!(name: 'ultimate_trial', title: 'Ultimate Trial') } + + describe '#up' do + let!(:premium_trial_plan_limits) { plan_limits.create!(plan_id: premium_trial_plan.id, ci_daily_pipeline_schedule_triggers: 0) } + let!(:ultimate_trial_plan_limits) { plan_limits.create!(plan_id: ultimate_trial_plan.id, ci_daily_pipeline_schedule_triggers: 0) } + + context 'when the environment is dev or com' do + before do + allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) + end + + it 'sets the trial plan limits for ci_daily_pipeline_schedule_triggers' do + disable_migrations_output { migrate! } + + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + end + + it 'does not change the plan limits if the ultimate trial plan is missing' do + ultimate_trial_plan.destroy! + + expect { disable_migrations_output { migrate! } }.not_to change { plan_limits.count } + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + end + + it 'does not change the plan limits if the ultimate trial plan limits is missing' do + ultimate_trial_plan_limits.destroy! + + expect { disable_migrations_output { migrate! } }.not_to change { plan_limits.count } + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + end + + it 'does not change the plan limits if the premium trial plan is missing' do + premium_trial_plan.destroy! + + expect { disable_migrations_output { migrate! } }.not_to change { plan_limits.count } + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + end + + it 'does not change the plan limits if the premium trial plan limits is missing' do + premium_trial_plan_limits.destroy! + + expect { disable_migrations_output { migrate! } }.not_to change { plan_limits.count } + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + end + end + + context 'when the environment is anything other than dev or com' do + before do + allow(Gitlab).to receive(:dev_env_or_com?).and_return(false) + end + + it 'does not update the plan limits' do + disable_migrations_output { migrate! } + + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + end + end + end + + describe '#down' do + let!(:premium_trial_plan_limits) { plan_limits.create!(plan_id: premium_trial_plan.id, ci_daily_pipeline_schedule_triggers: 288) } + let!(:ultimate_trial_plan_limits) { plan_limits.create!(plan_id: ultimate_trial_plan.id, ci_daily_pipeline_schedule_triggers: 288) } + + context 'when the environment is dev or com' do + before do + allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) + end + + it 'sets the trial plan limits ci_daily_pipeline_schedule_triggers to zero' do + migrate_down! + + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(0) + end + + it 'does not change the plan limits if the ultimate trial plan is missing' do + ultimate_trial_plan.destroy! + + expect { migrate_down! }.not_to change { plan_limits.count } + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + end + + it 'does not change the plan limits if the ultimate trial plan limits is missing' do + ultimate_trial_plan_limits.destroy! + + expect { migrate_down! }.not_to change { plan_limits.count } + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + end + + it 'does not change the plan limits if the premium trial plan is missing' do + premium_trial_plan.destroy! + + expect { migrate_down! }.not_to change { plan_limits.count } + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + end + + it 'does not change the plan limits if the premium trial plan limits is missing' do + premium_trial_plan_limits.destroy! + + expect { migrate_down! }.not_to change { plan_limits.count } + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + end + end + + context 'when the environment is anything other than dev or com' do + before do + allow(Gitlab).to receive(:dev_env_or_com?).and_return(false) + end + + it 'does not change the ultimate trial plan limits' do + migrate_down! + + expect(ultimate_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + expect(premium_trial_plan_limits.reload.ci_daily_pipeline_schedule_triggers).to eq(288) + end + end + end + + def migrate_down! + disable_migrations_output do + migrate! + described_class.new.down + end + end +end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index fb1d157b360..35846b0d4ea 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -58,7 +58,7 @@ RSpec.describe MergeRequestWidgetEntity do data = described_class.new(resource, request: request, issues_links: true).as_json expect(data).to include(:issues_links) - expect(data[:issues_links]).to include(:assign_to_closing, :closing, :mentioned_but_not_closing) + expect(data[:issues_links]).to include(:assign_to_closing, :closing, :mentioned_but_not_closing, :closing_count, :mentioned_count) end it 'omits issue links by default' do |