diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-09 18:07:50 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-09 18:07:50 +0300 |
commit | 608d6aaa3d80a33862ca2c29d96bfd687b1a011b (patch) | |
tree | 665f96928bb42b40cbc34d70a09ee951f15fb468 /spec | |
parent | 6180f62ab34662c64103872b8352b25817b73a8d (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
8 files changed, 242 insertions, 28 deletions
diff --git a/spec/frontend/gl_form_spec.js b/spec/frontend/gl_form_spec.js index ab5627ce216..6ad9d9f4338 100644 --- a/spec/frontend/gl_form_spec.js +++ b/spec/frontend/gl_form_spec.js @@ -6,6 +6,47 @@ import '~/lib/utils/common_utils'; describe('GLForm', () => { const testContext = {}; + const mockGl = { + GfmAutoComplete: { + dataSources: { + commands: '/group/projects/-/autocomplete_sources/commands', + }, + }, + }; + + describe('Setting up GfmAutoComplete', () => { + describe('setupForm', () => { + let setupFormSpy; + + beforeEach(() => { + setupFormSpy = jest.spyOn(GLForm.prototype, 'setupForm'); + + testContext.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>'); + testContext.textarea = testContext.form.find('textarea'); + }); + + it('should be called with the global data source `windows.gl`', () => { + window.gl = { ...mockGl }; + testContext.glForm = new GLForm(testContext.form, {}, false); + + expect(setupFormSpy).toHaveBeenCalledTimes(1); + expect(setupFormSpy).toHaveBeenCalledWith(window.gl.GfmAutoComplete.dataSources, false); + }); + + it('should be called with the provided custom data source', () => { + window.gl = { ...mockGl }; + + const customDataSources = { + foobar: '/group/projects/-/autocomplete_sources/foobar', + }; + + testContext.glForm = new GLForm(testContext.form, {}, false, customDataSources); + + expect(setupFormSpy).toHaveBeenCalledTimes(1); + expect(setupFormSpy).toHaveBeenCalledWith(customDataSources, false); + }); + }); + }); describe('when instantiated', () => { beforeEach(() => { diff --git a/spec/frontend/lib/utils/scroll_utils_spec.js b/spec/frontend/lib/utils/scroll_utils_spec.js new file mode 100644 index 00000000000..d42e25b929c --- /dev/null +++ b/spec/frontend/lib/utils/scroll_utils_spec.js @@ -0,0 +1,21 @@ +import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; + +describe('isScrolledToBottom', () => { + const setScrollGetters = (getters) => { + Object.entries(getters).forEach(([name, value]) => { + jest.spyOn(Element.prototype, name, 'get').mockReturnValue(value); + }); + }; + + it.each` + context | scrollTop | scrollHeight | result + ${'returns false when not scrolled to bottom'} | ${0} | ${2000} | ${false} + ${'returns true when scrolled to bottom'} | ${1000} | ${2000} | ${true} + ${'returns true when scrolled to bottom with subpixel precision'} | ${999.25} | ${2000} | ${true} + ${'returns true when cannot scroll'} | ${0} | ${500} | ${true} + `('$context', ({ scrollTop, scrollHeight, result }) => { + setScrollGetters({ scrollTop, clientHeight: 1000, scrollHeight }); + + expect(isScrolledToBottom()).toBe(result); + }); +}); diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js index 3dc52730bb4..68ce07f86b9 100644 --- a/spec/frontend/vue_shared/components/markdown/field_spec.js +++ b/spec/frontend/vue_shared/components/markdown/field_spec.js @@ -1,3 +1,4 @@ +import $ from 'jquery'; import { nextTick } from 'vue'; import AxiosMockAdapter from 'axios-mock-adapter'; import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants'; @@ -6,7 +7,7 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import MarkdownFieldHeader from '~/vue_shared/components/markdown/header.vue'; import MarkdownToolbar from '~/vue_shared/components/markdown/toolbar.vue'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; jest.mock('~/behaviors/markdown/render_gfm'); @@ -75,6 +76,22 @@ describe('Markdown field component', () => { ); } + function createWrapper({ autocompleteDataSources = {} } = {}) { + subject = shallowMountExtended(MarkdownField, { + propsData: { + markdownDocsPath, + markdownPreviewPath, + isSubmitting: false, + textareaValue, + lines: [], + enablePreview: true, + restrictedToolBarItems, + showContentEditorSwitcher: false, + autocompleteDataSources, + }, + }); + } + const getPreviewLink = () => subject.findByTestId('preview-tab'); const getWriteLink = () => subject.findByTestId('write-tab'); const getMarkdownButton = () => subject.find('.js-md'); @@ -85,6 +102,7 @@ describe('Markdown field component', () => { const findDropzone = () => subject.find('.div-dropzone'); const findMarkdownHeader = () => subject.findComponent(MarkdownFieldHeader); const findMarkdownToolbar = () => subject.findComponent(MarkdownToolbar); + const findGlForm = () => $(subject.vm.$refs['gl-form']).data('glForm'); describe('mounted', () => { const previewHTML = ` @@ -101,6 +119,18 @@ describe('Markdown field component', () => { findDropzone().element.addEventListener('click', dropzoneSpy); }); + describe('GlForm', () => { + beforeEach(() => { + createWrapper({ autocompleteDataSources: { commands: '/foobar/-/autocomplete_sources' } }); + }); + + it('initializes GlForm with autocomplete data sources', () => { + expect(findGlForm().autoComplete.dataSources).toMatchObject({ + commands: '/foobar/-/autocomplete_sources', + }); + }); + }); + it('renders textarea inside backdrop', () => { expect(subject.find('.zen-backdrop textarea').element).not.toBeNull(); }); diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js index 12eda284aea..26b536984ff 100644 --- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js +++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js @@ -24,6 +24,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => { const formFieldName = 'form[markdown_field]'; const formFieldPlaceholder = 'Write some markdown'; const formFieldAriaLabel = 'Edit your content'; + const autocompleteDataSources = { commands: '/foobar/-/autcomplete_sources' }; let mock; const buildWrapper = ({ propsData = {}, attachTo, stubs = {} } = {}) => { @@ -35,6 +36,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => { markdownDocsPath, quickActionsDocsPath, enableAutocomplete, + autocompleteDataSources, enablePreview, formFieldProps: { id: formFieldId, @@ -68,18 +70,17 @@ describe('vue_shared/component/markdown/markdown_editor', () => { it('displays markdown field by default', () => { buildWrapper({ propsData: { supportsQuickActions: true } }); - expect(findMarkdownField().props()).toEqual( - expect.objectContaining({ - markdownPreviewPath: renderMarkdownPath, - quickActionsDocsPath, - canAttachFile: true, - enableAutocomplete, - textareaValue: value, - markdownDocsPath, - uploadsPath: window.uploads_path, - enablePreview, - }), - ); + expect(findMarkdownField().props()).toMatchObject({ + autocompleteDataSources, + markdownPreviewPath: renderMarkdownPath, + quickActionsDocsPath, + canAttachFile: true, + enableAutocomplete, + textareaValue: value, + markdownDocsPath, + uploadsPath: window.uploads_path, + enablePreview, + }); }); it('renders markdown field textarea', () => { diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js index 05476ef5ca0..a12ec23c15a 100644 --- a/spec/frontend/work_items/components/work_item_description_spec.js +++ b/spec/frontend/work_items/components/work_item_description_spec.js @@ -16,6 +16,7 @@ import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import workItemDescriptionSubscription from '~/work_items/graphql/work_item_description.subscription.graphql'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql'; +import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils'; import { updateWorkItemMutationResponse, workItemDescriptionSubscriptionResponse, @@ -102,6 +103,49 @@ describe('WorkItemDescription', () => { wrapper.destroy(); }); + describe('editing description with workItemsMvc FF enabled', () => { + beforeEach(() => { + workItemsMvc = true; + }); + + it('passes correct autocompletion data and preview markdown sources and enables quick actions', async () => { + const { + iid, + project: { fullPath }, + } = workItemQueryResponse.data.workItem; + + await createComponent({ isEditing: true }); + + expect(findMarkdownEditor().props()).toMatchObject({ + autocompleteDataSources: autocompleteDataSources(fullPath, iid), + supportsQuickActions: true, + renderMarkdownPath: markdownPreviewPath(fullPath, iid), + quickActionsDocsPath: wrapper.vm.$options.quickActionsDocsPath, + }); + }); + }); + + describe('editing description with workItemsMvc FF disabled', () => { + beforeEach(() => { + workItemsMvc = false; + }); + + it('passes correct autocompletion data and preview markdown sources', async () => { + const { + iid, + project: { fullPath }, + } = workItemQueryResponse.data.workItem; + + await createComponent({ isEditing: true }); + + expect(findMarkdownField().props()).toMatchObject({ + autocompleteDataSources: autocompleteDataSources(fullPath, iid), + markdownPreviewPath: markdownPreviewPath(fullPath, iid), + quickActionsDocsPath: wrapper.vm.$options.quickActionsDocsPath, + }); + }); + }); + describe.each([true, false])( 'editing description with workItemsMvc %workItemsMvcEnabled', (workItemsMvcEnabled) => { diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js new file mode 100644 index 00000000000..aa24b80cf08 --- /dev/null +++ b/spec/frontend/work_items/utils_spec.js @@ -0,0 +1,27 @@ +import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils'; + +describe('autocompleteDataSources', () => { + beforeEach(() => { + gon.relative_url_root = '/foobar'; + }); + + it('returns corrrect data sources', () => { + expect(autocompleteDataSources('project/group', '2')).toMatchObject({ + commands: '/foobar/project/group/-/autocomplete_sources/commands?type=WorkItem&type_id=2', + labels: '/foobar/project/group/-/autocomplete_sources/labels?type=WorkItem&type_id=2', + members: '/foobar/project/group/-/autocomplete_sources/members?type=WorkItem&type_id=2', + }); + }); +}); + +describe('markdownPreviewPath', () => { + beforeEach(() => { + gon.relative_url_root = '/foobar'; + }); + + it('returns corrrect data sources', () => { + expect(markdownPreviewPath('project/group', '2')).toEqual( + '/foobar/project/group/preview_markdown?target_type=WorkItem&target_id=2', + ); + }); +}); diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb index fe36b1006b0..14758f9690f 100644 --- a/spec/support/shared_examples/features/work_items_shared_examples.rb +++ b/spec/support/shared_examples/features/work_items_shared_examples.rb @@ -76,6 +76,22 @@ RSpec.shared_examples 'work items description' do end end + it 'autocompletes available quick actions', :aggregate_failures do + click_button "Edit description" + + find('[aria-label="Description"]').send_keys("/") + + wait_for_requests + + page.within('.atwho-container') do + expect(page).to have_text("title") + expect(page).to have_text("shrug") + expect(page).to have_text("tableflip") + expect(page).to have_text("close") + expect(page).to have_text("cc") + end + end + context 'on conflict' do let_it_be(:other_user) { create(:user) } let(:expected_warning) { 'Someone edited the description at the same time you did.' } diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb index f98be12523d..5755b9a56b1 100644 --- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| describe attribute do describe '#increment_counter', :redis do let(:amount) { 10 } - let(:increment) { Gitlab::Counters::Increment.new(amount: amount) } + let(:increment) { Gitlab::Counters::Increment.new(amount: amount, ref: 3) } let(:counter_key) { model.counter(attribute).key } subject { model.increment_counter(attribute, increment) } @@ -31,6 +31,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| attribute: attribute, project_id: model.project_id, increment: amount, + ref: increment.ref, new_counter_value: 0 + amount, current_db_value: model.read_attribute(attribute), 'correlation_id' => an_instance_of(String), @@ -74,27 +75,36 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end describe '#bulk_increment_counter', :redis do - let(:increments) { [Gitlab::Counters::Increment.new(amount: 10), Gitlab::Counters::Increment.new(amount: 5)] } + let(:increments) do + [ + Gitlab::Counters::Increment.new(amount: 10, ref: 1), + Gitlab::Counters::Increment.new(amount: 5, ref: 2) + ] + end + let(:total_amount) { increments.sum(&:amount) } let(:counter_key) { model.counter(attribute).key } subject { model.bulk_increment_counter(attribute, increments) } context 'when attribute is a counter attribute' do - it 'increments the counter in Redis and logs it' do - expect(Gitlab::AppLogger).to receive(:info).with( - hash_including( - message: 'Increment counter attribute', - attribute: attribute, - project_id: model.project_id, - increment: total_amount, - new_counter_value: 0 + total_amount, - current_db_value: model.read_attribute(attribute), - 'correlation_id' => an_instance_of(String), - 'meta.feature_category' => 'test', - 'meta.caller_id' => 'caller' + it 'increments the counter in Redis and logs each increment' do + increments.each do |increment| + expect(Gitlab::AppLogger).to receive(:info).with( + hash_including( + message: 'Increment counter attribute', + attribute: attribute, + project_id: model.project_id, + increment: increment.amount, + ref: increment.ref, + new_counter_value: 0 + total_amount, + current_db_value: model.read_attribute(attribute), + 'correlation_id' => an_instance_of(String), + 'meta.feature_category' => 'test', + 'meta.caller_id' => 'caller' + ) ) - ) + end subject @@ -104,6 +114,30 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end end + context 'when feature flag split_log_bulk_increment_counter is disabled' do + before do + stub_feature_flags(split_log_bulk_increment_counter: false) + end + + it 'logs a single total increment' do + expect(Gitlab::AppLogger).to receive(:info).with( + hash_including( + message: 'Increment counter attribute', + attribute: attribute, + project_id: model.project_id, + increment: increments.sum(&:amount), + new_counter_value: 0 + total_amount, + current_db_value: model.read_attribute(attribute), + 'correlation_id' => an_instance_of(String), + 'meta.feature_category' => 'test', + 'meta.caller_id' => 'caller' + ) + ) + + subject + end + end + it 'does not increment the counter for the record' do expect { subject }.not_to change { model.reset.read_attribute(attribute) } end |