diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-15 21:09:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-15 21:09:09 +0300 |
commit | da1962d9ac710f95d350d2645c87f5a663123cf2 (patch) | |
tree | 1725ade126a9b4ae0148cd100cee94c44f9ce9f3 /spec | |
parent | e69e3f1eb695b4e852c56e7ddf8c52915ae2631b (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
17 files changed, 656 insertions, 115 deletions
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap index 65c658536a3..161c2bade05 100644 --- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap +++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap @@ -10,57 +10,81 @@ exports[`Code navigation popover component renders popover 1`] = ` style="left: 0px;" /> - <div - class="overflow-auto code-navigation-popover-container" + <gl-tabs-stub + contentclass="gl-py-0" + nav-class="gl-hidden" + theme="indigo" > - <div - class="" + <gl-tab-stub + title="Definition" > - <pre - class="border-0 bg-transparent m-0 code highlight text-wrap" + <div + class="overflow-auto code-navigation-popover-container" > - <span - class="line" - lang="javascript" + <div + class="" > - <span - class="k" + <pre + class="border-0 bg-transparent m-0 code highlight text-wrap" > - function - </span> - <span> - main() { - </span> - </span> - <span - class="line" - lang="javascript" + <span + class="line" + lang="javascript" + > + <span + class="k" + > + function + </span> + <span> + main() { + </span> + </span> + <span + class="line" + lang="javascript" + > + <span> + } + </span> + </span> + </pre> + </div> + </div> + + <div + class="popover-body border-top" + > + <gl-button-stub + category="tertiary" + class="w-100" + data-testid="go-to-definition-btn" + href="http://gitlab.com/test.js" + icon="" + size="medium" + target="_blank" + variant="default" > - <span> - } - </span> - </span> - </pre> - </div> - </div> - - <div - class="popover-body border-top" - > - <gl-button-stub - category="tertiary" - class="w-100" - data-testid="go-to-definition-btn" - href="http://gitlab.com/test.js" - icon="" - size="medium" - target="_blank" - variant="default" + + Go to definition + + </gl-button-stub> + </div> + </gl-tab-stub> + + <gl-tab-stub + class="py-2" + data-testid="references-tab" > + + <p + class="gl-my-4 gl-px-4" + > + + No references found - Go to definition - - </gl-button-stub> - </div> + </p> + </gl-tab-stub> + </gl-tabs-stub> </div> `; diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js index d5a72ab8af8..7b323cfab72 100644 --- a/spec/frontend/code_navigation/components/popover_spec.js +++ b/spec/frontend/code_navigation/components/popover_spec.js @@ -40,6 +40,17 @@ const MOCK_DOCS_DATA = Object.freeze({ definition_path: 'test.js#L20', }); +const MOCK_DATA_WITH_REFERENCES = Object.freeze({ + hover: [ + { + language: null, + value: 'console.log', + }, + ], + references: [{ path: 'index.js' }, { path: 'app.js' }], + definition_path: 'test.js#L20', +}); + let wrapper; function factory({ position, data, definitionPathPrefix, blobPath = 'index.js' }) { @@ -64,6 +75,16 @@ describe('Code navigation popover component', () => { expect(wrapper.element).toMatchSnapshot(); }); + it('srender references tab with empty text when no references exist', () => { + factory({ + position: { x: 0, y: 0, height: 0 }, + data: MOCK_CODE_DATA, + definitionPathPrefix: DEFINITION_PATH_PREFIX, + }); + + expect(wrapper.find('[data-testid="references-tab"]').text()).toContain('No references found'); + }); + it('renders link with hash to current file', () => { factory({ position: { x: 0, y: 0, height: 0 }, @@ -75,6 +96,17 @@ describe('Code navigation popover component', () => { expect(wrapper.find('[data-testid="go-to-definition-btn"]').attributes('href')).toBe('#L20'); }); + it('renders list of references', () => { + factory({ + position: { x: 0, y: 0, height: 0 }, + data: MOCK_DATA_WITH_REFERENCES, + definitionPathPrefix: DEFINITION_PATH_PREFIX, + }); + + expect(wrapper.find('[data-testid="references-tab"]').exists()).toBe(true); + expect(wrapper.findAll('[data-testid="reference-link"]').length).toBe(2); + }); + describe('code output', () => { it('renders code output', () => { factory({ diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js index 7915544814a..3d27b9c3051 100644 --- a/spec/frontend/integrations/edit/components/integration_form_spec.js +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -88,17 +88,17 @@ describe('IntegrationForm', () => { expect(findJiraTriggerFields().exists()).toBe(true); }); - describe('featureFlag jiraIntegration is false', () => { + describe('featureFlag jiraIssuesIntegration is false', () => { it('does not render JiraIssuesFields', () => { - createComponent({ type: 'jira' }, { jiraIntegration: false }); + createComponent({ type: 'jira' }, { jiraIssuesIntegration: false }); expect(findJiraIssuesFields().exists()).toBe(false); }); }); - describe('featureFlag jiraIntegration is true', () => { + describe('featureFlag jiraIssuesIntegration is true', () => { it('renders JiraIssuesFields', () => { - createComponent({ type: 'jira' }, { jiraIntegration: true }); + createComponent({ type: 'jira' }, { jiraIssuesIntegration: true }); expect(findJiraIssuesFields().exists()).toBe(true); }); diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index 85e680fe216..e769580b587 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -595,6 +595,14 @@ describe('URL utility', () => { ); }); + it('handles arrays properly when railsArraySyntax=true', () => { + const url = 'https://gitlab.com/test'; + + expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url, false, true)).toEqual( + 'https://gitlab.com/test?labels%5B%5D=foo&labels%5B%5D=bar', + ); + }); + it('removes all existing URL params and sets a new param when cleanParams=true', () => { const url = 'https://gitlab.com/test?group_id=gitlab-org&project_id=my-project'; diff --git a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap new file mode 100644 index 00000000000..4d5b6c56a34 --- /dev/null +++ b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Project remove modal initialized matches the snapshot 1`] = ` +<form + action="some/path" + method="post" +> + <input + name="_method" + type="hidden" + value="delete" + /> + + <input + name="authenticity_token" + type="hidden" + /> + + <b-button-stub + class="[object Object]" + event="click" + role="button" + routertag="a" + size="md" + tabindex="0" + tag="button" + type="button" + variant="danger" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + Remove project + </span> + </b-button-stub> + + <b-modal-stub + canceltitle="Cancel" + cancelvariant="secondary" + footerclass="bg-gray-light gl-p-5" + headerclosecontent="×" + headercloselabel="Close" + id="remove-project-modal" + ignoreenforcefocusselector="" + lazy="true" + modalclass="gl-modal," + oktitle="OK" + okvariant="danger" + size="sm" + title="" + titletag="h4" + > + + <div> + <p + class="gl-text-red-500 gl-font-weight-bold" + > + This can lead to data loss. + </p> + + <p + class="gl-mb-0" + > + This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention. + </p> + + <p> + <gl-sprintf-stub + message="Please type %{phrase_code} to proceed or close this modal to cancel." + /> + </p> + + <gl-form-input-stub + id="confirm_name_input" + name="confirm_name_input" + type="text" + /> + </div> + + <template /> + + <template> + Confirmation required + </template> + + <template /> + + <template /> + + <template /> + + <template> + <div + class="gl-w-full gl-display-flex gl-just-content-start gl-m-0" + > + <b-button-stub + class="[object Object]" + disabled="true" + event="click" + routertag="a" + size="md" + tag="button" + type="button" + variant="danger" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + + Confirm + + </span> + </b-button-stub> + </div> + </template> + </b-modal-stub> +</form> +`; diff --git a/spec/frontend/projects/components/remove_modal_spec.js b/spec/frontend/projects/components/remove_modal_spec.js new file mode 100644 index 00000000000..339aee65b99 --- /dev/null +++ b/spec/frontend/projects/components/remove_modal_spec.js @@ -0,0 +1,62 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlButton, GlModal } from '@gitlab/ui'; +import ProjectRemoveModal from '~/projects/components/remove_modal.vue'; + +describe('Project remove modal', () => { + let wrapper; + + const findFormElement = () => wrapper.find('form').element; + const findConfirmButton = () => wrapper.find(GlModal).find(GlButton); + + const defaultProps = { + formPath: 'some/path', + confirmPhrase: 'foo', + warningMessage: 'This can lead to data loss.', + }; + + const createComponent = (data = {}) => { + wrapper = shallowMount(ProjectRemoveModal, { + propsData: defaultProps, + data: () => data, + stubs: { + GlButton, + GlModal, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('initialized', () => { + beforeEach(() => { + createComponent(); + }); + + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + }); + + describe('user input matches the confirmPhrase', () => { + beforeEach(() => { + createComponent({ userInput: defaultProps.confirmPhrase }); + }); + + it('the confirm button is not dislabled', () => { + expect(findConfirmButton().attributes('disabled')).toBe(undefined); + }); + + describe('and when the confirmation button is clicked', () => { + beforeEach(() => { + findConfirmButton().vm.$emit('click'); + }); + + it('submits the form element', () => { + expect(findFormElement().submit).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js index 2253db7cbd0..0007aed5c4d 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js @@ -2,8 +2,9 @@ import { buildUneditableOpenTokens, buildUneditableCloseToken, buildUneditableCloseTokens, - buildUneditableInlineTokens, buildUneditableTokens, + buildUneditableInlineTokens, + buildUneditableHtmlAsTextTokens, } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; import { @@ -12,6 +13,7 @@ import { uneditableOpenTokens, uneditableCloseToken, uneditableCloseTokens, + uneditableBlockTokens, uneditableInlineTokens, uneditableTokens, } from './mock_data'; @@ -41,6 +43,15 @@ describe('Build Uneditable Token renderer helper', () => { }); }); + describe('buildUneditableTokens', () => { + it('returns a 3-item array of tokens with the originToken wrapped in the middle of block tokens', () => { + const result = buildUneditableTokens(originToken); + + expect(result).toHaveLength(3); + expect(result).toStrictEqual(uneditableTokens); + }); + }); + describe('buildUneditableInlineTokens', () => { it('returns a 3-item array of tokens with the originInlineToken wrapped in the middle of inline tokens', () => { const result = buildUneditableInlineTokens(originInlineToken); @@ -50,12 +61,20 @@ describe('Build Uneditable Token renderer helper', () => { }); }); - describe('buildUneditableTokens', () => { - it('returns a 3-item array of tokens with the originToken wrapped in the middle of block tokens', () => { - const result = buildUneditableTokens(originToken); + describe('buildUneditableHtmlAsTextTokens', () => { + it('returns a 3-item array of tokens with the htmlBlockNode wrapped as a text token in the middle of block tokens', () => { + const htmlBlockNode = { + type: 'htmlBlock', + literal: '<div data-tomark-pass ><h1>Some header</h1><p>Some paragraph</p></div>', + }; + const result = buildUneditableHtmlAsTextTokens(htmlBlockNode); + const { type, content } = result[1]; + + expect(type).toBe('text'); + expect(content).not.toMatch(/ data-tomark-pass /); expect(result).toHaveLength(3); - expect(result).toStrictEqual(uneditableTokens); + expect(result).toStrictEqual(uneditableBlockTokens); }); }); }); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js index 0c010a20d98..433f41774b4 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js @@ -12,7 +12,7 @@ export const normalTextNode = buildMockTextNode('This is just normal text.'); // Token spec helpers -const buildUneditableOpenToken = type => { +const buildMockUneditableOpenToken = type => { return { type: 'openTag', tagName: type, @@ -23,7 +23,7 @@ const buildUneditableOpenToken = type => { }; }; -const buildUneditableCloseToken = type => { +const buildMockUneditableCloseToken = type => { return { type: 'closeTag', tagName: type }; }; @@ -31,8 +31,8 @@ export const originToken = { type: 'text', content: '{:.no_toc .hidden-md .hidden-lg}', }; -export const uneditableCloseToken = buildUneditableCloseToken('div'); -export const uneditableOpenTokens = [buildUneditableOpenToken('div'), originToken]; +export const uneditableCloseToken = buildMockUneditableCloseToken('div'); +export const uneditableOpenTokens = [buildMockUneditableOpenToken('div'), originToken]; export const uneditableCloseTokens = [originToken, uneditableCloseToken]; export const uneditableTokens = [...uneditableOpenTokens, uneditableCloseToken]; @@ -41,7 +41,17 @@ export const originInlineToken = { content: '<i>Inline</i> content', }; export const uneditableInlineTokens = [ - buildUneditableOpenToken('span'), + buildMockUneditableOpenToken('a'), originInlineToken, - buildUneditableCloseToken('span'), + buildMockUneditableCloseToken('a'), +]; + +export const uneditableBlockTokens = [ + buildMockUneditableOpenToken('div'), + { + type: 'text', + tagName: null, + content: '<div><h1>Some header</h1><p>Some paragraph</p></div>', + }, + buildMockUneditableCloseToken('div'), ]; diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js new file mode 100644 index 00000000000..a6c712eeb31 --- /dev/null +++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js @@ -0,0 +1,38 @@ +import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block'; +import { buildUneditableHtmlAsTextTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; + +import { normalTextNode } from './mock_data'; + +const htmlBlockNode = { + firstChild: null, + literal: '<div><h1>Heading</h1><p>Paragraph.</p></div>', + type: 'htmlBlock', +}; + +describe('Render HTML renderer', () => { + describe('canRender', () => { + it('should return true when the argument is an html block', () => { + expect(renderer.canRender(htmlBlockNode)).toBe(true); + }); + + it('should return false when the argument is not an html block', () => { + expect(renderer.canRender(normalTextNode)).toBe(false); + }); + }); + + describe('render', () => { + const htmlBlockNodeToMark = { + firstChild: null, + literal: '<div data-to-mark ></div>', + type: 'htmlBlock', + }; + + it.each` + node + ${htmlBlockNode} + ${htmlBlockNodeToMark} + `('should return uneditable tokens wrapping the $node as a token', ({ node }) => { + expect(renderer.render(node)).toStrictEqual(buildUneditableHtmlAsTextTokens(node)); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_spec.js deleted file mode 100644 index c863b86ebf6..00000000000 --- a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html'; -import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; - -import { normalTextNode } from './mock_data'; - -const htmlLiteral = '<div><h1>Heading</h1><p>Paragraph.</p></div>'; -const htmlBlockNode = { - firstChild: null, - literal: htmlLiteral, - type: 'htmlBlock', -}; - -describe('Render HTML renderer', () => { - describe('canRender', () => { - it('should return true when the argument is an html block', () => { - expect(renderer.canRender(htmlBlockNode)).toBe(true); - }); - - it('should return false when the argument is not an html block', () => { - expect(renderer.canRender(normalTextNode)).toBe(false); - }); - }); - - describe('render', () => { - it('should return uneditable tokens wrapping the origin token', () => { - const origin = jest.fn(); - const context = { origin }; - - expect(renderer.render(htmlBlockNode, context)).toStrictEqual( - buildUneditableTokens(origin()), - ); - }); - }); -}); diff --git a/spec/haml_lint/linter/documentation_links_spec.rb b/spec/haml_lint/linter/documentation_links_spec.rb new file mode 100644 index 00000000000..68de8317b82 --- /dev/null +++ b/spec/haml_lint/linter/documentation_links_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'haml_lint' +require 'haml_lint/spec' +require Rails.root.join('haml_lint/linter/documentation_links') + +RSpec.describe HamlLint::Linter::DocumentationLinks do + include_context 'linter' + + context 'when link_to points to the existing file path' do + let(:haml) { "= link_to 'Description', help_page_path('README.md')" } + + it { is_expected.not_to report_lint } + end + + context 'when link_to points to the existing file with valid anchor' do + let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'overview'), target: '_blank'" } + + it { is_expected.not_to report_lint } + end + + context 'when link_to points to the existing file path without .md extension' do + let(:haml) { "= link_to 'Description', help_page_path('README')" } + + it { is_expected.not_to report_lint } + end + + context 'when anchor is not correct' do + let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'wrong')" } + + it { is_expected.to report_lint } + + context 'when help_page_path has multiple options' do + let(:haml) { "= link_to 'Description', help_page_path('README.md', key: :value, anchor: 'wrong')" } + + it { is_expected.to report_lint } + end + end + + context 'when file path is wrong' do + let(:haml) { "= link_to 'Description', help_page_path('wrong.md'), target: '_blank'" } + + it { is_expected.to report_lint } + end + + context 'when link with wrong file path is assigned to a variable' do + let(:haml) { "- my_link = link_to 'Description', help_page_path('wrong.md')" } + + it { is_expected.to report_lint } + end + + context 'when it is a broken code' do + let(:haml) { "= I am broken! ]]]]" } + + it { is_expected.not_to report_lint } + end + + context 'when anchor belongs to a different element' do + let(:haml) { "= link_to 'Description', help_page_path('README.md'), target: (anchor: 'blank')" } + + it { is_expected.not_to report_lint } + end + + context 'when a simple help_page_path' do + let(:haml) { "- url = help_page_path('wrong.md')" } + + it { is_expected.to report_lint } + end + + context 'when link is not a string' do + let(:haml) { "- url = help_page_path(help_url)" } + + it { is_expected.not_to report_lint } + end + + context 'when link is a part of the tag' do + let(:haml) { ".data-form{ data: { url: help_page_path('wrong.md') } }" } + + it { is_expected.to report_lint } + end +end diff --git a/spec/lib/api/entities/merge_request_approvals_spec.rb b/spec/lib/api/entities/merge_request_approvals_spec.rb new file mode 100644 index 00000000000..cbbb037100a --- /dev/null +++ b/spec/lib/api/entities/merge_request_approvals_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Entities::MergeRequestApprovals do + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + + subject { described_class.new(merge_request, current_user: user).as_json } + + before do + merge_request.project.add_developer(user) + end + + it 'serializes an approved merge request' do + create(:approval, merge_request: merge_request, user: user) + + is_expected.to eq({ + user_has_approved: true, + user_can_approve: false, + approved: true, + approved_by: [{ + user: API::Entities::UserBasic.new(user).as_json + }] + }) + end + + it 'serializes a merge request that is not approved' do + is_expected.to eq({ + user_has_approved: false, + user_can_approve: true, + approved: false, + approved_by: [] + }) + end +end diff --git a/spec/lib/gitlab/danger/changelog_spec.rb b/spec/lib/gitlab/danger/changelog_spec.rb index 3e9990a45fe..f5954cd8c1e 100644 --- a/spec/lib/gitlab/danger/changelog_spec.rb +++ b/spec/lib/gitlab/danger/changelog_spec.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true require 'fast_spec_helper' -require 'rspec-parameterized' require_relative 'danger_spec_helper' require 'gitlab/danger/changelog' RSpec.describe Gitlab::Danger::Changelog do - using RSpec::Parameterized::TableSyntax include DangerSpecHelper let(:added_files) { nil } @@ -26,34 +24,36 @@ RSpec.describe Gitlab::Danger::Changelog do subject(:changelog) { fake_danger.new(git: fake_git, gitlab: fake_gitlab, helper: fake_helper) } describe '#needed?' do - subject { changelog.needed? } + let(:category_with_changelog) { :backend } + let(:label_with_changelog) { 'frontend' } + let(:category_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_CATEGORIES.first } + let(:label_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_LABELS.first } - where(:categories, :labels) do - { backend: nil } | %w[backend backstage] - { frontend: nil, docs: nil } | ['ci-build'] - { engineering_productivity: nil, none: nil } | ['meta'] - end + subject { changelog.needed? } - with_them do - let(:changes_by_category) { categories } - let(:mr_labels) { labels } + context 'when MR contains only categories requiring no changelog' do + let(:changes_by_category) { { category_without_changelog => nil } } + let(:mr_labels) { [] } - it "is falsy when categories and labels require no changelog" do + it 'is falsey' do is_expected.to be_falsy end end - where(:categories, :labels) do - { frontend: nil, docs: nil } | ['database::review pending', 'feature'] - { backend: nil } | ['backend', 'technical debt'] - { engineering_productivity: nil, none: nil } | ['frontend'] + context 'when MR contains a label that require no changelog' do + let(:changes_by_category) { { category_with_changelog => nil } } + let(:mr_labels) { [label_with_changelog, label_without_changelog] } + + it 'is falsey' do + is_expected.to be_falsy + end end - with_them do - let(:changes_by_category) { categories } - let(:mr_labels) { labels } + context 'when MR contains a category that require changelog and a category that require no changelog' do + let(:changes_by_category) { { category_with_changelog => nil, category_without_changelog => nil } } + let(:mr_labels) { [] } - it "is truthy when categories and labels require a changelog" do + it 'is truthy' do is_expected.to be_truthy end end diff --git a/spec/lib/gitlab/utils/markdown_spec.rb b/spec/lib/gitlab/utils/markdown_spec.rb new file mode 100644 index 00000000000..001ff5bc487 --- /dev/null +++ b/spec/lib/gitlab/utils/markdown_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Utils::Markdown do + let(:klass) do + Class.new do + include Gitlab::Utils::Markdown + end + end + + subject(:object) { klass.new } + + describe '#string_to_anchor' do + subject { object.string_to_anchor(string) } + + let(:string) { 'My Header' } + + it 'converts string to anchor' do + is_expected.to eq 'my-header' + end + + context 'when string has punctuation' do + let(:string) { 'My, Header!' } + + it 'removes punctuation' do + is_expected.to eq 'my-header' + end + end + + context 'when string starts and ends with spaces' do + let(:string) { ' My Header ' } + + it 'removes extra spaces' do + is_expected.to eq 'my-header' + end + end + + context 'when string has multiple spaces and dashes in the middle' do + let(:string) { 'My - - - Header' } + + it 'removes consecutive dashes' do + is_expected.to eq 'my-header' + end + end + + context 'when string contains only digits' do + let(:string) { '123' } + + it 'adds anchor prefix' do + is_expected.to eq 'anchor-123' + end + end + + context 'when string is empty' do + let(:string) { '' } + + it 'returns an empty string' do + is_expected.to eq '' + end + end + end +end diff --git a/spec/models/concerns/approvable_base_spec.rb b/spec/models/concerns/approvable_base_spec.rb new file mode 100644 index 00000000000..e4aded1b8d0 --- /dev/null +++ b/spec/models/concerns/approvable_base_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ApprovableBase do + describe '#has_approved?' do + let(:merge_request) { create(:merge_request) } + let(:user) { create(:user) } + + subject { merge_request.has_approved?(user) } + + context 'when a user has not approved' do + it 'returns false' do + is_expected.to be_falsy + end + end + + context 'when a user has approved' do + let!(:approval) { create(:approval, merge_request: merge_request, user: user) } + + it 'returns false' do + is_expected.to be_truthy + end + end + + context 'when a user is nil' do + let(:user) { nil } + + it 'returns false' do + is_expected.to be_falsy + end + end + end +end diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index e7184d23767..f1e581efd44 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -613,4 +613,22 @@ RSpec.describe MergeRequestPresenter do end end end + + describe '#api_approvals_path' do + subject { described_class.new(resource, current_user: user).api_approvals_path } + + it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/approvals")) } + end + + describe '#api_approve_path' do + subject { described_class.new(resource, current_user: user).api_approve_path } + + it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/approve")) } + end + + describe '#api_unapprove_path' do + subject { described_class.new(resource, current_user: user).api_unapprove_path } + + it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/unapprove")) } + end end diff --git a/spec/requests/api/ci/runner_spec.rb b/spec/requests/api/ci/runner_spec.rb index 8106b7195c8..c8718309bf2 100644 --- a/spec/requests/api/ci/runner_spec.rb +++ b/spec/requests/api/ci/runner_spec.rb @@ -1823,13 +1823,36 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do expect(json_response['ProcessLsif']).to be_truthy end + it 'adds ProcessLsifReferences header' do + authorize_artifacts_with_token_in_headers(artifact_type: :lsif) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['ProcessLsifReferences']).to be_truthy + end + context 'code_navigation feature flag is disabled' do - it 'does not add ProcessLsif header' do + it 'responds with a forbidden error' do stub_feature_flags(code_navigation: false) + authorize_artifacts_with_token_in_headers(artifact_type: :lsif) + + aggregate_failures do + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['ProcessLsif']).to be_falsy + expect(json_response['ProcessLsifReferences']).to be_falsy + end + end + end + context 'code_navigation_references feature flag is disabled' do + it 'sets ProcessLsifReferences header to false' do + stub_feature_flags(code_navigation_references: false) authorize_artifacts_with_token_in_headers(artifact_type: :lsif) - expect(response).to have_gitlab_http_status(:forbidden) + aggregate_failures do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['ProcessLsif']).to be_truthy + expect(json_response['ProcessLsifReferences']).to be_falsy + end end end end |