diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-23 15:13:18 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-23 15:13:18 +0300 |
commit | 1ccebc7b3f091ef585dc87a91847aa35f7ae2130 (patch) | |
tree | 2e7970962dd429c3dd98ca09e52004df6b3aaa30 /spec | |
parent | fd767b7d65e0e9cb6f313b1803b859b8a7c0da57 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
9 files changed, 403 insertions, 277 deletions
diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb index 9fb85957979..ec22201b88f 100644 --- a/spec/features/merge_request/user_manages_subscription_spec.rb +++ b/spec/features/merge_request/user_manages_subscription_spec.rb @@ -50,15 +50,11 @@ RSpec.describe 'User manages subscription', :js do wait_for_requests - click_button 'Toggle dropdown' - expect(page).to have_selector('.gl-toggle.is-checked') find('[data-testid="notifications-toggle"] .gl-toggle').click wait_for_requests - click_button 'Toggle dropdown' - expect(page).to have_selector('.gl-toggle:not(.is-checked)') end end diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js index b37d2f06191..83aced63475 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js @@ -1,15 +1,12 @@ import { nextTick } from 'vue'; -import { GlAlert, GlButton, GlFormInput, GlFormGroup, GlSegmentedControl } from '@gitlab/ui'; +import { GlAlert, GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { mockTracking } from 'helpers/tracking_helper'; -import { stubComponent } from 'helpers/stub_component'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import ContentEditor from '~/content_editor/components/content_editor.vue'; -import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import WikiForm from '~/pages/shared/wikis/components/wiki_form.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import { CONTENT_EDITOR_LOADED_ACTION, SAVED_USING_CONTENT_EDITOR_ACTION, @@ -18,8 +15,6 @@ import { WIKI_FORMAT_UPDATED_ACTION, } from '~/pages/shared/wikis/constants'; -import MarkdownField from '~/vue_shared/components/markdown/field.vue'; - jest.mock('~/emoji'); describe('WikiForm', () => { @@ -30,16 +25,12 @@ describe('WikiForm', () => { const findForm = () => wrapper.find('form'); const findTitle = () => wrapper.find('#wiki_title'); const findFormat = () => wrapper.find('#wiki_format'); - const findContent = () => wrapper.find('#wiki_content'); const findMessage = () => wrapper.find('#wiki_message'); + const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor); const findSubmitButton = () => wrapper.findByTestId('wiki-submit-button'); const findCancelButton = () => wrapper.findByTestId('wiki-cancel-button'); - const findToggleEditingModeButton = () => wrapper.findByTestId('toggle-editing-mode-button'); const findTitleHelpLink = () => wrapper.findByText('Learn more.'); const findMarkdownHelpLink = () => wrapper.findByTestId('wiki-markdown-help-link'); - const findContentEditor = () => wrapper.findComponent(ContentEditor); - const findClassicEditor = () => wrapper.findComponent(MarkdownField); - const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); const setFormat = (value) => { const format = findFormat(); @@ -103,11 +94,8 @@ describe('WikiForm', () => { }, }, stubs: { - MarkdownField, GlAlert, GlButton, - GlSegmentedControl, - LocalStorageSync: stubComponent(LocalStorageSync), GlFormInput, GlFormGroup, }, @@ -126,6 +114,22 @@ describe('WikiForm', () => { wrapper = null; }); + it('displays markdown editor', () => { + createWrapper({ persisted: true }); + + expect(findMarkdownEditor().props()).toEqual( + expect.objectContaining({ + value: pageInfoPersisted.content, + renderMarkdownPath: pageInfoPersisted.markdownPreviewPath, + markdownDocsPath: pageInfoPersisted.markdownHelpPath, + uploadsPath: pageInfoPersisted.uploadsPath, + autofocus: pageInfoPersisted.persisted, + formFieldId: 'wiki_content', + formFieldName: 'wiki[content]', + }), + ); + }); + it.each` title | persisted | message ${'my page'} | ${false} | ${'Create my page'} @@ -154,7 +158,7 @@ describe('WikiForm', () => { it('does not trim page content by default', () => { createWrapper({ persisted: true }); - expect(findContent().element.value).toBe(' My page content '); + expect(findMarkdownEditor().props().value).toBe(' My page content '); }); it.each` @@ -168,7 +172,9 @@ describe('WikiForm', () => { await setFormat(format); - expect(findClassicEditor().props('enablePreview')).toBe(enabled); + nextTick(); + + expect(findMarkdownEditor().props('enablePreview')).toBe(enabled); }); it.each` @@ -219,9 +225,7 @@ describe('WikiForm', () => { beforeEach(async () => { createWrapper({ mountFn: mount, persisted: true }); - const input = findContent(); - - await input.setValue(' Lorem ipsum dolar sit! '); + await findMarkdownEditor().vm.$emit('input', ' Lorem ipsum dolar sit! '); }); it('sets before unload warning', () => { @@ -245,7 +249,7 @@ describe('WikiForm', () => { }); it('does not trim page content', () => { - expect(findContent().element.value).toBe(' Lorem ipsum dolar sit! '); + expect(findMarkdownEditor().props().value).toBe(' Lorem ipsum dolar sit! '); }); }); }); @@ -264,7 +268,7 @@ describe('WikiForm', () => { createWrapper({ mountFn: mount }); await findTitle().setValue(title); - await findContent().setValue(content); + await findMarkdownEditor().vm.$emit('input', content); expect(findSubmitButton().props().disabled).toBe(disabledAttr); }, @@ -296,208 +300,64 @@ describe('WikiForm', () => { ); }); - describe('toggle editing mode control', () => { - beforeEach(() => { - createWrapper({ mountFn: mount }); - }); + it.each` + format | enabled | action + ${'markdown'} | ${true} | ${'enables'} + ${'rdoc'} | ${false} | ${'disables'} + ${'asciidoc'} | ${false} | ${'disables'} + ${'org'} | ${false} | ${'disables'} + `('$action content editor when format is $format', async ({ format, enabled }) => { + createWrapper({ mountFn: mount }); - it.each` - format | exists | action - ${'markdown'} | ${true} | ${'displays'} - ${'rdoc'} | ${false} | ${'hides'} - ${'asciidoc'} | ${false} | ${'hides'} - ${'org'} | ${false} | ${'hides'} - `('$action toggle editing mode button when format is $format', async ({ format, exists }) => { - await setFormat(format); - - expect(findToggleEditingModeButton().exists()).toBe(exists); - }); + setFormat(format); - describe('when content editor is not active', () => { - it('displays "Source" label in the toggle editing mode button', () => { - expect(findToggleEditingModeButton().props().checked).toBe('source'); - }); + await nextTick(); - describe('when clicking the toggle editing mode button', () => { - beforeEach(async () => { - await findToggleEditingModeButton().vm.$emit('input', 'richText'); - }); + expect(findMarkdownEditor().props().enableContentEditor).toBe(enabled); + }); - it('hides the classic editor', () => { - expect(findClassicEditor().exists()).toBe(false); - }); + describe('when markdown editor activates the content editor', () => { + beforeEach(async () => { + createWrapper({ mountFn: mount, persisted: true }); - it('shows the content editor', () => { - expect(findContentEditor().exists()).toBe(true); - }); - }); + await findMarkdownEditor().vm.$emit('contentEditor'); }); - describe('markdown editor type persistance', () => { - it('loads content editor by default if it is persisted in local storage', async () => { - expect(findClassicEditor().exists()).toBe(true); - expect(findContentEditor().exists()).toBe(false); - - // enable content editor - await findLocalStorageSync().vm.$emit('input', 'richText'); - - expect(findContentEditor().exists()).toBe(true); - expect(findClassicEditor().exists()).toBe(false); - }); + it('disables the format dropdown', () => { + expect(findFormat().element.getAttribute('disabled')).toBeDefined(); }); - describe('when content editor is active', () => { - beforeEach(() => { - createWrapper(); - findToggleEditingModeButton().vm.$emit('input', 'richText'); - }); - - it('displays "Edit Rich" label in the toggle editing mode button', () => { - expect(findToggleEditingModeButton().props().checked).toBe('richText'); - }); - - describe('when clicking the toggle editing mode button', () => { - beforeEach(async () => { - await findToggleEditingModeButton().vm.$emit('input', 'source'); - await nextTick(); - }); - - it('hides the content editor', () => { - expect(findContentEditor().exists()).toBe(false); - }); - - it('displays the classic editor', () => { - expect(findClassicEditor().exists()).toBe(true); - }); - }); - - describe('when content editor is loading', () => { - beforeEach(async () => { - findContentEditor().vm.$emit('loading'); - - await nextTick(); - }); - - it('disables toggle editing mode button', () => { - expect(findToggleEditingModeButton().attributes().disabled).toBe('true'); - }); - - describe('when content editor loads successfully', () => { - it('enables toggle editing mode button', async () => { - findContentEditor().vm.$emit('loadingSuccess'); - - await nextTick(); - - expect(findToggleEditingModeButton().attributes().disabled).not.toBeDefined(); - }); - }); - - describe('when content editor fails to load', () => { - it('enables toggle editing mode button', async () => { - findContentEditor().vm.$emit('loadingError'); - - await nextTick(); - - expect(findToggleEditingModeButton().attributes().disabled).not.toBeDefined(); - }); - }); + it('sends tracking event when editor loads', async () => { + expect(trackingSpy).toHaveBeenCalledWith(undefined, CONTENT_EDITOR_LOADED_ACTION, { + label: WIKI_CONTENT_EDITOR_TRACKING_LABEL, }); }); - }); - describe('wiki content editor', () => { - describe('clicking "Edit rich text": editor fails to load', () => { - beforeEach(async () => { - createWrapper({ mountFn: mount }); - mock.onPost(/preview-markdown/).reply(400); + describe('when triggering form submit', () => { + const updatedMarkdown = 'hello **world**'; - await findToggleEditingModeButton().vm.$emit('input', 'richText'); - - // try waiting for content editor to load (but it will never actually load) - await waitForPromises(); - }); - - it('disables the submit button', () => { - expect(findSubmitButton().props('disabled')).toBe(true); - }); - - describe('toggling editing modes to the classic editor', () => { - beforeEach(() => { - return findToggleEditingModeButton().vm.$emit('input', 'source'); - }); - - it('switches to classic editor', () => { - expect(findContentEditor().exists()).toBe(false); - expect(findClassicEditor().exists()).toBe(true); - }); - }); - }); - - describe('clicking "Edit rich text": editor loads successfully', () => { beforeEach(async () => { - createWrapper({ persisted: true, mountFn: mount }); - - mock.onPost(/preview-markdown/).reply(200, { body: '<p>hello <strong>world</strong></p>' }); - - await findToggleEditingModeButton().vm.$emit('input', 'richText'); - await waitForPromises(); + findMarkdownEditor().vm.$emit('input', updatedMarkdown); + await triggerFormSubmit(); }); - it('shows the rich text editor when loading finishes', async () => { - expect(findContentEditor().exists()).toBe(true); + it('unsets before unload warning on form submit', async () => { + const e = dispatchBeforeUnload(); + expect(e.preventDefault).not.toHaveBeenCalled(); }); - it('sends tracking event when editor loads', async () => { - expect(trackingSpy).toHaveBeenCalledWith(undefined, CONTENT_EDITOR_LOADED_ACTION, { + it('triggers tracking events on form submit', async () => { + expect(trackingSpy).toHaveBeenCalledWith(undefined, SAVED_USING_CONTENT_EDITOR_ACTION, { label: WIKI_CONTENT_EDITOR_TRACKING_LABEL, }); - }); - - it('disables the format dropdown', () => { - expect(findFormat().element.getAttribute('disabled')).toBeDefined(); - }); - - describe('when wiki content is updated', () => { - const updatedMarkdown = 'hello **world**'; - - beforeEach(() => { - findContentEditor().vm.$emit('change', { - empty: false, - changed: true, - markdown: updatedMarkdown, - }); - }); - - it('sets before unload warning', () => { - const e = dispatchBeforeUnload(); - expect(e.preventDefault).toHaveBeenCalledTimes(1); - }); - - it('unsets before unload warning on form submit', async () => { - await triggerFormSubmit(); - - const e = dispatchBeforeUnload(); - expect(e.preventDefault).not.toHaveBeenCalled(); - }); - it('triggers tracking events on form submit', async () => { - await triggerFormSubmit(); - expect(trackingSpy).toHaveBeenCalledWith(undefined, SAVED_USING_CONTENT_EDITOR_ACTION, { - label: WIKI_CONTENT_EDITOR_TRACKING_LABEL, - }); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, WIKI_FORMAT_UPDATED_ACTION, { - label: WIKI_FORMAT_LABEL, - extra: { - value: findFormat().element.value, - old_format: pageInfoPersisted.format, - project_path: pageInfoPersisted.path, - }, - }); - }); - - it('sets content field to the content editor updated markdown', async () => { - expect(findContent().element.value).toBe(updatedMarkdown); + expect(trackingSpy).toHaveBeenCalledWith(undefined, WIKI_FORMAT_UPDATED_ACTION, { + label: WIKI_FORMAT_LABEL, + extra: { + value: findFormat().element.value, + old_format: pageInfoPersisted.format, + project_path: pageInfoPersisted.path, + }, }); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js new file mode 100644 index 00000000000..da3777de1d0 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js @@ -0,0 +1,226 @@ +import { GlSegmentedControl } from '@gitlab/ui'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { EDITING_MODE_MARKDOWN_FIELD, EDITING_MODE_CONTENT_EDITOR } from '~/vue_shared/constants'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; +import ContentEditor from '~/content_editor/components/content_editor.vue'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; + +jest.mock('~/emoji'); + +describe('vue_shared/component/markdown/markdown_editor', () => { + let wrapper; + const value = 'test markdown'; + const renderMarkdownPath = '/api/markdown'; + const markdownDocsPath = '/help/markdown'; + const uploadsPath = '/uploads'; + const enableAutocomplete = true; + const enablePreview = false; + const formFieldId = 'markdown_field'; + const formFieldName = 'form[markdown_field]'; + const formFieldPlaceholder = 'Write some markdown'; + const formFieldAriaLabel = 'Edit your content'; + let mock; + + const buildWrapper = (propsData = {}) => { + wrapper = mountExtended(MarkdownEditor, { + propsData: { + value, + renderMarkdownPath, + markdownDocsPath, + uploadsPath, + enableAutocomplete, + enablePreview, + formFieldId, + formFieldName, + formFieldPlaceholder, + formFieldAriaLabel, + ...propsData, + }, + }); + }; + const findSegmentedControl = () => wrapper.findComponent(GlSegmentedControl); + const findMarkdownField = () => wrapper.findComponent(MarkdownField); + const findTextarea = () => wrapper.find('textarea'); + const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); + const findContentEditor = () => wrapper.findComponent(ContentEditor); + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + wrapper.destroy(); + mock.restore(); + }); + + it('displays markdown field by default', () => { + buildWrapper(); + + expect(findMarkdownField().props()).toEqual( + expect.objectContaining({ + markdownPreviewPath: renderMarkdownPath, + canAttachFile: true, + enableAutocomplete, + textareaValue: value, + markdownDocsPath, + uploadsPath, + enablePreview, + }), + ); + }); + + it('renders markdown field textarea', () => { + buildWrapper(); + + expect(findTextarea().attributes()).toEqual( + expect.objectContaining({ + id: formFieldId, + name: formFieldName, + placeholder: formFieldPlaceholder, + 'aria-label': formFieldAriaLabel, + }), + ); + + expect(findTextarea().element.value).toBe(value); + }); + + it('renders switch segmented control', () => { + buildWrapper(); + + expect(findSegmentedControl().props()).toEqual({ + checked: EDITING_MODE_MARKDOWN_FIELD, + options: [ + { + text: expect.any(String), + value: EDITING_MODE_MARKDOWN_FIELD, + }, + { + text: expect.any(String), + value: EDITING_MODE_CONTENT_EDITOR, + }, + ], + }); + }); + + describe.each` + editingMode + ${EDITING_MODE_CONTENT_EDITOR} + ${EDITING_MODE_MARKDOWN_FIELD} + `('when segmented control emits change event with $editingMode value', ({ editingMode }) => { + it(`emits ${editingMode} event`, () => { + buildWrapper(); + + findSegmentedControl().vm.$emit('change', editingMode); + + expect(wrapper.emitted(editingMode)).toHaveLength(1); + }); + }); + + describe(`when editingMode is ${EDITING_MODE_MARKDOWN_FIELD}`, () => { + it('emits input event when markdown field textarea changes', async () => { + buildWrapper(); + const newValue = 'new value'; + + await findTextarea().setValue(newValue); + + expect(wrapper.emitted('input')).toEqual([[newValue]]); + }); + + describe(`when segmented control triggers input event with ${EDITING_MODE_CONTENT_EDITOR} value`, () => { + beforeEach(() => { + buildWrapper(); + findSegmentedControl().vm.$emit('input', EDITING_MODE_CONTENT_EDITOR); + }); + + it('displays the content editor', () => { + expect(findContentEditor().props()).toEqual( + expect.objectContaining({ + renderMarkdown: expect.any(Function), + uploadsPath, + markdown: value, + }), + ); + }); + + it('adds hidden field with current markdown', () => { + const hiddenField = wrapper.find(`#${formFieldId}`); + + expect(hiddenField.attributes()).toEqual( + expect.objectContaining({ + id: formFieldId, + name: formFieldName, + }), + ); + expect(hiddenField.element.value).toBe(value); + }); + + it('hides the markdown field', () => { + expect(findMarkdownField().exists()).toBe(false); + }); + + it('updates localStorage value', () => { + expect(findLocalStorageSync().props().value).toBe(EDITING_MODE_CONTENT_EDITOR); + }); + }); + }); + + describe(`when editingMode is ${EDITING_MODE_CONTENT_EDITOR}`, () => { + beforeEach(() => { + buildWrapper(); + findSegmentedControl().vm.$emit('input', EDITING_MODE_CONTENT_EDITOR); + }); + + it('emits input event when content editor emits change event', async () => { + const newValue = 'new value'; + + await findContentEditor().vm.$emit('change', { markdown: newValue }); + + expect(wrapper.emitted('input')).toEqual([[newValue]]); + }); + + describe(`when segmented control triggers input event with ${EDITING_MODE_MARKDOWN_FIELD} value`, () => { + beforeEach(() => { + findSegmentedControl().vm.$emit('input', EDITING_MODE_MARKDOWN_FIELD); + }); + + it('hides the content editor', () => { + expect(findContentEditor().exists()).toBe(false); + }); + + it('shows the markdown field', () => { + expect(findMarkdownField().exists()).toBe(true); + }); + + it('updates localStorage value', () => { + expect(findLocalStorageSync().props().value).toBe(EDITING_MODE_MARKDOWN_FIELD); + }); + }); + + describe('when content editor emits loading event', () => { + beforeEach(() => { + findContentEditor().vm.$emit('loading'); + }); + + it('disables switch editing mode control', () => { + // This is the only way that I found to check the segmented control is disabled + expect(findSegmentedControl().find('input[disabled]').exists()).toBe(true); + }); + + describe.each` + event + ${'loadingSuccess'} + ${'loadingError'} + `('when content editor emits $event event', ({ event }) => { + beforeEach(() => { + findContentEditor().vm.$emit(event); + }); + it('enables the switch editing mode control', () => { + expect(findSegmentedControl().find('input[disabled]').exists()).toBe(false); + }); + }); + }); + }); +}); diff --git a/spec/lib/gitlab/background_migration/backfill_internal_on_notes_spec.rb b/spec/lib/gitlab/background_migration/backfill_internal_on_notes_spec.rb new file mode 100644 index 00000000000..40a4758ba5f --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_internal_on_notes_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillInternalOnNotes, :migration, schema: 20220920124709 do + let(:notes_table) { table(:notes) } + + let!(:confidential_note) { notes_table.create!(id: 1, confidential: true, internal: false) } + let!(:non_confidential_note) { notes_table.create!(id: 2, confidential: false, internal: false) } + + describe '#perform' do + subject(:perform) do + described_class.new( + start_id: 1, + end_id: 2, + batch_table: :notes, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ApplicationRecord.connection + ).perform + end + + it 'backfills internal column on notes when confidential' do + expect { perform } + .to change { confidential_note.reload.internal }.from(false).to(true) + .and not_change { non_confidential_note.reload.internal } + end + end +end diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb index 234ba68d627..a22aa30304b 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb @@ -122,19 +122,17 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do context 'when compare_to is branch or tag' do using RSpec::Parameterized::TableSyntax - where(:pipeline_ref, :compare_to, :paths, :ff, :result) do - 'feature_1' | 'master' | ['file1.txt'] | true | true - 'feature_1' | 'master' | ['README.md'] | true | false - 'feature_1' | 'master' | ['xyz.md'] | true | false - 'feature_2' | 'master' | ['file1.txt'] | true | true - 'feature_2' | 'master' | ['file2.txt'] | true | true - 'feature_2' | 'feature_1' | ['file1.txt'] | true | false - 'feature_2' | 'feature_1' | ['file1.txt'] | false | true - 'feature_2' | 'feature_1' | ['file2.txt'] | true | true - 'feature_1' | 'tag_1' | ['file1.txt'] | true | false - 'feature_1' | 'tag_1' | ['file1.txt'] | false | true - 'feature_1' | 'tag_1' | ['file2.txt'] | true | true - 'feature_2' | 'tag_1' | ['file2.txt'] | true | true + where(:pipeline_ref, :compare_to, :paths, :result) do + 'feature_1' | 'master' | ['file1.txt'] | true + 'feature_1' | 'master' | ['README.md'] | false + 'feature_1' | 'master' | ['xyz.md'] | false + 'feature_2' | 'master' | ['file1.txt'] | true + 'feature_2' | 'master' | ['file2.txt'] | true + 'feature_2' | 'feature_1' | ['file1.txt'] | false + 'feature_2' | 'feature_1' | ['file2.txt'] | true + 'feature_1' | 'tag_1' | ['file1.txt'] | false + 'feature_1' | 'tag_1' | ['file2.txt'] | true + 'feature_2' | 'tag_1' | ['file2.txt'] | true end with_them do @@ -144,10 +142,6 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do build(:ci_pipeline, project: project, ref: pipeline_ref, sha: project.commit(pipeline_ref).sha) end - before do - stub_feature_flags(ci_rules_changes_compare: ff) - end - it { is_expected.to eq(result) } end end @@ -174,14 +168,6 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do ::Gitlab::Ci::Build::Rules::Rule::Clause::ParseError, 'rules:changes:compare_to is not a valid ref' ) end - - context 'when the FF ci_rules_changes_compare is disabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it { is_expected.to be_truthy } - end end end end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb index 8f9c3573897..9ff3ad1dfc0 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb @@ -11,14 +11,21 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_git let(:expected_value) { 4 } - it_behaves_like 'a correct instrumented metric value', { options: { event: 'pushes', prefix: 'source_code' } } + it_behaves_like 'a correct instrumented metric value', { + options: { event: 'pushes', prefix: 'source_code' }, + time_frame: 'all' + } it 'raises an exception if event option is not present' do - expect { described_class.new(prefix: 'source_code') }.to raise_error(ArgumentError) + expect do + described_class.new(options: { prefix: 'source_code' }, time_frame: 'all') + end.to raise_error(ArgumentError, /'event' option is required/) end it 'raises an exception if prefix option is not present' do - expect { described_class.new(event: 'pushes') }.to raise_error(ArgumentError) + expect do + described_class.new(options: { event: 'pushes' }, time_frame: 'all') + end.to raise_error(ArgumentError, /'prefix' option is required/) end describe 'children classes' do @@ -55,7 +62,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_git end it_behaves_like 'a correct instrumented metric value', { - options: { event: 'merge_requests_count', prefix: 'web_ide', include_usage_prefix: false } + options: { event: 'merge_requests_count', prefix: 'web_ide', include_usage_prefix: false }, + time_frame: 'all' } end diff --git a/spec/migrations/20220920124709_backfill_internal_on_notes_spec.rb b/spec/migrations/20220920124709_backfill_internal_on_notes_spec.rb new file mode 100644 index 00000000000..f4ac6e6fc8e --- /dev/null +++ b/spec/migrations/20220920124709_backfill_internal_on_notes_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillInternalOnNotes, :migration do + let(:migration) { described_class::MIGRATION } + + describe '#up' do + it 'schedules background jobs for each batch of issues' do + migrate! + + expect(migration).to have_scheduled_batched_migration( + table_name: :notes, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2e3d5302a76..79f2565e173 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -5516,4 +5516,34 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end end + + describe '#notes=' do + context 'when notes already exist' do + it 'does not create duplicate notes', :aggregate_failures do + time = Time.zone.now + pipeline = create(:ci_pipeline, user: user, project: project) + note = Note.new( + note: 'note', + noteable_type: 'Commit', + noteable_id: pipeline.id, + commit_id: pipeline.id, + author_id: user.id, + project_id: pipeline.project_id, + created_at: time + ) + another_note = note.dup.tap { |note| note.note = 'another note' } + + expect(project.notes.for_commit_id(pipeline.sha).count).to eq(0) + + pipeline.notes = [note] + + expect(project.notes.for_commit_id(pipeline.sha).count).to eq(1) + + pipeline.notes = [note, note, another_note] + + expect(project.notes.for_commit_id(pipeline.sha).count).to eq(2) + expect(project.notes.for_commit_id(pipeline.sha).pluck(:note)).to contain_exactly(note.note, another_note.note) + end + end + end end diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb index fc57ca66d3a..87e9b4400e7 100644 --- a/spec/services/ci/create_pipeline_service/rules_spec.rb +++ b/spec/services/ci/create_pipeline_service/rules_spec.rb @@ -544,16 +544,6 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes 'Failed to parse rule for job1: rules:changes:compare_to is not a valid ref' ]) end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(build_names).to contain_exactly('job1', 'job2') - end - end end context 'when the compare_to ref exists' do @@ -563,16 +553,6 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes it 'creates job1 and job2' do expect(build_names).to contain_exactly('job1', 'job2') end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(build_names).to contain_exactly('job1', 'job2') - end - end end context 'when the rule does not match' do @@ -581,16 +561,6 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes it 'does not create job1' do expect(build_names).to contain_exactly('job2') end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(build_names).to contain_exactly('job1', 'job2') - end - end end end end @@ -616,17 +586,6 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes expect(pipeline).to be_created_successfully expect(build_names).to contain_exactly('job1') end - - context 'when the FF ci_rules_changes_compare is not enabled' do - before do - stub_feature_flags(ci_rules_changes_compare: false) - end - - it 'ignores compare_to and changes is always true' do - expect(pipeline).to be_created_successfully - expect(build_names).to contain_exactly('job1') - end - end end context 'when the rule does not match' do |