2023-06-20 00:43:36 +05:30
|
|
|
import { GlLink, GlFormCheckbox } from '@gitlab/ui';
|
2021-03-08 18:12:59 +05:30
|
|
|
import { nextTick } from 'vue';
|
2020-06-23 00:09:42 +05:30
|
|
|
import batchComments from '~/batch_comments/stores/modules/batch_comments';
|
2021-03-11 19:13:27 +05:30
|
|
|
import NoteForm from '~/notes/components/note_form.vue';
|
|
|
|
import createStore from '~/notes/stores';
|
2019-07-07 11:18:12 +05:30
|
|
|
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
2022-08-27 11:52:29 +05:30
|
|
|
import { AT_WHO_ACTIVE_CLASS } from '~/gfm_auto_complete';
|
2023-06-20 00:43:36 +05:30
|
|
|
import eventHub from '~/environments/event_hub';
|
|
|
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
2022-07-16 23:28:13 +05:30
|
|
|
import { noteableDataMock, notesDataMock, discussionMock, note } from '../mock_data';
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
jest.mock('~/lib/utils/autosave');
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe('issue_note_form component', () => {
|
2018-11-08 19:23:39 +05:30
|
|
|
let store;
|
2019-07-07 11:18:12 +05:30
|
|
|
let wrapper;
|
2018-03-17 18:26:18 +05:30
|
|
|
let props;
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
const createComponentWrapper = (propsData = {}, provide = {}) => {
|
|
|
|
wrapper = mountExtended(NoteForm, {
|
2019-07-07 11:18:12 +05:30
|
|
|
store,
|
2023-06-20 00:43:36 +05:30
|
|
|
propsData: {
|
|
|
|
...props,
|
|
|
|
...propsData,
|
|
|
|
},
|
|
|
|
provide: {
|
|
|
|
glFeatures: provide,
|
|
|
|
},
|
2019-07-07 11:18:12 +05:30
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
const findCancelButton = () => wrapper.findByTestId('cancel');
|
|
|
|
const findCancelCommentButton = () => wrapper.findByTestId('cancelBatchCommentsEnabled');
|
|
|
|
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
|
2021-03-11 19:13:27 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
beforeEach(() => {
|
2018-11-08 19:23:39 +05:30
|
|
|
store = createStore();
|
2018-03-17 18:26:18 +05:30
|
|
|
store.dispatch('setNoteableData', noteableDataMock);
|
|
|
|
store.dispatch('setNotesData', notesDataMock);
|
|
|
|
|
|
|
|
props = {
|
|
|
|
isEditing: false,
|
|
|
|
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
|
2018-11-20 20:47:30 +05:30
|
|
|
noteId: '545',
|
2018-03-17 18:26:18 +05:30
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2018-11-20 20:47:30 +05:30
|
|
|
describe('noteHash', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
beforeEach(() => {
|
2023-06-20 00:43:36 +05:30
|
|
|
createComponentWrapper();
|
2019-07-07 11:18:12 +05:30
|
|
|
});
|
|
|
|
|
2018-11-20 20:47:30 +05:30
|
|
|
it('returns note hash string based on `noteId`', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`);
|
2018-11-20 20:47:30 +05:30
|
|
|
});
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
it('return note hash as `#` when `noteId` is empty', () => {
|
|
|
|
createComponentWrapper({
|
2019-07-07 11:18:12 +05:30
|
|
|
noteId: '',
|
|
|
|
});
|
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
expect(wrapper.vm.noteHash).toBe('#');
|
2018-11-20 20:47:30 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
it('hides content editor switcher if feature flag content_editor_on_issues is off', () => {
|
|
|
|
createComponentWrapper({}, { contentEditorOnIssues: false });
|
|
|
|
|
|
|
|
expect(wrapper.text()).not.toContain('Rich text');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('shows content editor switcher if feature flag content_editor_on_issues is on', () => {
|
|
|
|
createComponentWrapper({}, { contentEditorOnIssues: true });
|
|
|
|
|
|
|
|
expect(wrapper.text()).toContain('Rich text');
|
|
|
|
});
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe('conflicts editing', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
beforeEach(() => {
|
2023-06-20 00:43:36 +05:30
|
|
|
createComponentWrapper();
|
2019-07-07 11:18:12 +05:30
|
|
|
});
|
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
it('should show conflict message if note changes outside the component', async () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
wrapper.setProps({
|
|
|
|
...props,
|
|
|
|
noteBody: 'Foo',
|
|
|
|
});
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
const message =
|
2021-04-17 20:07:23 +05:30
|
|
|
'This comment changed after you started editing it. Review the updated comment to ensure information is not lost.';
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
await nextTick();
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
const conflictWarning = wrapper.find('.js-conflict-edit-warning');
|
|
|
|
|
|
|
|
expect(conflictWarning.exists()).toBe(true);
|
|
|
|
expect(conflictWarning.text().replace(/\s+/g, ' ').trim()).toBe(message);
|
2022-08-27 11:52:29 +05:30
|
|
|
expect(conflictWarning.findComponent(GlLink).attributes('href')).toBe('#note_545');
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('form', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
beforeEach(() => {
|
2023-06-20 00:43:36 +05:30
|
|
|
createComponentWrapper();
|
2019-07-07 11:18:12 +05:30
|
|
|
});
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
it('should render text area with placeholder', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
const textarea = wrapper.find('textarea');
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(textarea.attributes('placeholder')).toBe('Write a comment or drag your files here…');
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
it('should set data-supports-quick-actions to enable autocomplete', () => {
|
|
|
|
const textarea = wrapper.find('textarea');
|
|
|
|
|
|
|
|
expect(textarea.attributes('data-supports-quick-actions')).toBe('true');
|
|
|
|
});
|
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
it.each`
|
2022-10-11 01:57:18 +05:30
|
|
|
internal | placeholder
|
|
|
|
${false} | ${'Write a comment or drag your files here…'}
|
|
|
|
${true} | ${'Write an internal note or drag your files here…'}
|
2022-07-16 23:28:13 +05:30
|
|
|
`(
|
2022-10-11 01:57:18 +05:30
|
|
|
'should set correct textarea placeholder text when discussion confidentiality is $internal',
|
2023-06-20 00:43:36 +05:30
|
|
|
async ({ internal, placeholder }) => {
|
2022-07-16 23:28:13 +05:30
|
|
|
props.note = {
|
|
|
|
...note,
|
2022-10-11 01:57:18 +05:30
|
|
|
internal,
|
2022-07-16 23:28:13 +05:30
|
|
|
};
|
2023-06-20 00:43:36 +05:30
|
|
|
createComponentWrapper();
|
|
|
|
|
|
|
|
await nextTick();
|
2022-07-16 23:28:13 +05:30
|
|
|
|
|
|
|
expect(wrapper.find('textarea').attributes('placeholder')).toBe(placeholder);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
it('should link to markdown docs', () => {
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(findMarkdownField().props('markdownDocsPath')).toBe(notesDataMock.markdownDocsPath);
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('keyboard events', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
let textarea;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
textarea = wrapper.find('textarea');
|
|
|
|
textarea.setValue('Foo');
|
|
|
|
});
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe('up', () => {
|
|
|
|
it('should ender edit mode', () => {
|
2023-06-20 00:43:36 +05:30
|
|
|
const eventHubSpy = jest.spyOn(eventHub, '$emit');
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
textarea.trigger('keydown.up');
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(eventHubSpy).not.toHaveBeenCalled();
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('enter', () => {
|
|
|
|
it('should save note when cmd+enter is pressed', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
textarea.trigger('keydown.enter', { metaKey: true });
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.emitted('handleFormUpdate')).toHaveLength(1);
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
it('should save note when ctrl+enter is pressed', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
textarea.trigger('keydown.enter', { ctrlKey: true });
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.emitted('handleFormUpdate')).toHaveLength(1);
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
2021-11-18 22:05:49 +05:30
|
|
|
|
|
|
|
it('should disable textarea when ctrl+enter is pressed', async () => {
|
|
|
|
textarea.trigger('keydown.enter', { ctrlKey: true });
|
|
|
|
|
|
|
|
expect(textarea.attributes('disabled')).toBeUndefined();
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
expect(textarea.attributes('disabled')).toBe('disabled');
|
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('actions', () => {
|
2023-06-20 00:43:36 +05:30
|
|
|
it('should be possible to cancel', () => {
|
|
|
|
createComponentWrapper();
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
findCancelButton().vm.$emit('click');
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.emitted('cancelForm')).toHaveLength(1);
|
2019-05-18 00:54:41 +05:30
|
|
|
});
|
|
|
|
|
2022-08-27 11:52:29 +05:30
|
|
|
it('will not cancel form if there is an active at-who-active class', async () => {
|
2023-06-20 00:43:36 +05:30
|
|
|
createComponentWrapper();
|
2022-08-27 11:52:29 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
const textareaEl = wrapper.vm.$refs.markdownEditor.$el.querySelector('textarea');
|
2022-08-27 11:52:29 +05:30
|
|
|
const cancelButton = findCancelButton();
|
|
|
|
textareaEl.classList.add(AT_WHO_ACTIVE_CLASS);
|
|
|
|
cancelButton.vm.$emit('click');
|
|
|
|
await nextTick();
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.emitted('cancelForm')).toBeUndefined();
|
2022-08-27 11:52:29 +05:30
|
|
|
});
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
it('should be possible to update the note', () => {
|
|
|
|
createComponentWrapper();
|
2019-05-18 00:54:41 +05:30
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
const textarea = wrapper.find('textarea');
|
|
|
|
textarea.setValue('Foo');
|
|
|
|
const saveButton = wrapper.find('.js-vue-issue-save');
|
2021-03-11 19:13:27 +05:30
|
|
|
saveButton.vm.$emit('click');
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.emitted('handleFormUpdate')).toHaveLength(1);
|
2019-07-07 11:18:12 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
describe('with batch comments', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
store.registerModule('batchComments', batchComments());
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
createComponentWrapper({
|
2021-01-29 00:20:46 +05:30
|
|
|
isDraft: true,
|
2020-06-23 00:09:42 +05:30
|
|
|
noteId: '',
|
|
|
|
discussion: { ...discussionMock, for_commit: false },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
it('should be possible to cancel', () => {
|
|
|
|
findCancelCommentButton().vm.$emit('click');
|
2020-06-23 00:09:42 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.emitted('cancelForm')).toEqual([[true, false]]);
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('shows resolve checkbox', () => {
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.findComponent(GlFormCheckbox).exists()).toBe(true);
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
it('hides resolve checkbox', () => {
|
|
|
|
createComponentWrapper({
|
2021-01-29 00:20:46 +05:30
|
|
|
isDraft: false,
|
|
|
|
discussion: {
|
|
|
|
...discussionMock,
|
|
|
|
notes: [
|
2021-03-08 18:12:59 +05:30
|
|
|
...discussionMock.notes.map((n) => ({
|
2021-01-29 00:20:46 +05:30
|
|
|
...n,
|
|
|
|
resolvable: true,
|
|
|
|
current_user: { ...n.current_user, can_resolve_discussion: false },
|
|
|
|
})),
|
|
|
|
],
|
|
|
|
for_commit: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(wrapper.findComponent(GlFormCheckbox).exists()).toBe(false);
|
2021-01-29 00:20:46 +05:30
|
|
|
});
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
it('hides actions for commits', () => {
|
|
|
|
createComponentWrapper({ discussion: { for_commit: true } });
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
expect(wrapper.find('.note-form-actions').text()).not.toContain('Start a review');
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('on enter', () => {
|
2021-03-11 19:13:27 +05:30
|
|
|
it('should start review or add to review when cmd+enter is pressed', async () => {
|
2020-06-23 00:09:42 +05:30
|
|
|
const textarea = wrapper.find('textarea');
|
|
|
|
|
|
|
|
textarea.setValue('Foo');
|
|
|
|
textarea.trigger('keydown.enter', { metaKey: true });
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
await nextTick();
|
2023-06-20 00:43:36 +05:30
|
|
|
|
|
|
|
expect(wrapper.emitted('handleFormUpdateAddToReview')).toEqual([['Foo', false]]);
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|