Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-09 18:07:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-09 18:07:50 +0300
commit608d6aaa3d80a33862ca2c29d96bfd687b1a011b (patch)
tree665f96928bb42b40cbc34d70a09ee951f15fb468 /spec
parent6180f62ab34662c64103872b8352b25817b73a8d (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/gl_form_spec.js41
-rw-r--r--spec/frontend/lib/utils/scroll_utils_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js32
-rw-r--r--spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js25
-rw-r--r--spec/frontend/work_items/components/work_item_description_spec.js44
-rw-r--r--spec/frontend/work_items/utils_spec.js27
-rw-r--r--spec/support/shared_examples/features/work_items_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb64
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