import { shallowMount, createLocalVue } from '@vue/test-utils'; import createStore from '~/notes/stores'; import NoteForm from '~/notes/components/note_form.vue'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import { noteableDataMock, notesDataMock } from '../mock_data'; describe('issue_note_form component', () => { const dummyAutosaveKey = 'some-autosave-key'; const dummyDraft = 'dummy draft content'; let store; let wrapper; let props; const createComponentWrapper = () => { const localVue = createLocalVue(); return shallowMount(localVue.extend(NoteForm), { store, propsData: props, // see https://gitlab.com/gitlab-org/gitlab-foss/issues/56317 for the following localVue, }); }; beforeEach(() => { spyOnDependency(NoteForm, 'getDraft').and.callFake(key => { if (key === dummyAutosaveKey) { return dummyDraft; } return null; }); store = createStore(); store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); props = { isEditing: false, noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.', noteId: '545', }; }); afterEach(() => { wrapper.destroy(); }); describe('noteHash', () => { beforeEach(() => { wrapper = createComponentWrapper(); }); it('returns note hash string based on `noteId`', () => { expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`); }); it('return note hash as `#` when `noteId` is empty', done => { wrapper.setProps({ ...props, noteId: '', }); wrapper.vm .$nextTick() .then(() => { expect(wrapper.vm.noteHash).toBe('#'); }) .then(done) .catch(done.fail); }); }); describe('conflicts editing', () => { beforeEach(() => { wrapper = createComponentWrapper(); }); it('should show conflict message if note changes outside the component', done => { wrapper.setProps({ ...props, isEditing: true, noteBody: 'Foo', }); const message = 'This comment has changed since you started editing, please review the updated comment to ensure information is not lost.'; wrapper.vm .$nextTick() .then(() => { const conflictWarning = wrapper.find('.js-conflict-edit-warning'); expect(conflictWarning.exists()).toBe(true); expect( conflictWarning .text() .replace(/\s+/g, ' ') .trim(), ).toBe(message); }) .then(done) .catch(done.fail); }); }); describe('form', () => { beforeEach(() => { wrapper = createComponentWrapper(); }); it('should render text area with placeholder', () => { const textarea = wrapper.find('textarea'); expect(textarea.attributes('placeholder')).toEqual( 'Write a comment or drag your files hereā€¦', ); }); it('should link to markdown docs', () => { const { markdownDocsPath } = notesDataMock; const markdownField = wrapper.find(MarkdownField); const markdownFieldProps = markdownField.props(); expect(markdownFieldProps.markdownDocsPath).toBe(markdownDocsPath); }); describe('keyboard events', () => { let textarea; beforeEach(() => { textarea = wrapper.find('textarea'); textarea.setValue('Foo'); }); describe('up', () => { it('should ender edit mode', () => { // TODO: do not spy on vm spyOn(wrapper.vm, 'editMyLastNote').and.callThrough(); textarea.trigger('keydown.up'); expect(wrapper.vm.editMyLastNote).toHaveBeenCalled(); }); }); describe('enter', () => { it('should save note when cmd+enter is pressed', () => { textarea.trigger('keydown.enter', { metaKey: true }); const { handleFormUpdate } = wrapper.emitted(); expect(handleFormUpdate.length).toBe(1); }); it('should save note when ctrl+enter is pressed', () => { textarea.trigger('keydown.enter', { ctrlKey: true }); const { handleFormUpdate } = wrapper.emitted(); expect(handleFormUpdate.length).toBe(1); }); }); }); describe('actions', () => { it('should be possible to cancel', done => { // TODO: do not spy on vm spyOn(wrapper.vm, 'cancelHandler').and.callThrough(); wrapper.setProps({ ...props, isEditing: true, }); wrapper.vm .$nextTick() .then(() => { const cancelButton = wrapper.find('.note-edit-cancel'); cancelButton.trigger('click'); expect(wrapper.vm.cancelHandler).toHaveBeenCalled(); }) .then(done) .catch(done.fail); }); it('should be possible to update the note', done => { wrapper.setProps({ ...props, isEditing: true, }); wrapper.vm .$nextTick() .then(() => { const textarea = wrapper.find('textarea'); textarea.setValue('Foo'); const saveButton = wrapper.find('.js-vue-issue-save'); saveButton.trigger('click'); expect(wrapper.vm.isSubmitting).toEqual(true); }) .then(done) .catch(done.fail); }); }); }); describe('with autosaveKey', () => { describe('with draft', () => { beforeEach(done => { Object.assign(props, { noteBody: '', autosaveKey: dummyAutosaveKey, }); wrapper = createComponentWrapper(); wrapper.vm .$nextTick() .then(done) .catch(done.fail); }); it('displays the draft in textarea', () => { const textarea = wrapper.find('textarea'); expect(textarea.element.value).toBe(dummyDraft); }); }); describe('without draft', () => { beforeEach(done => { Object.assign(props, { noteBody: '', autosaveKey: 'some key without draft', }); wrapper = createComponentWrapper(); wrapper.vm .$nextTick() .then(done) .catch(done.fail); }); it('leaves the textarea empty', () => { const textarea = wrapper.find('textarea'); expect(textarea.element.value).toBe(''); }); }); it('updates the draft if textarea content changes', () => { const updateDraftSpy = spyOnDependency(NoteForm, 'updateDraft').and.stub(); Object.assign(props, { noteBody: '', autosaveKey: dummyAutosaveKey, }); wrapper = createComponentWrapper(); const textarea = wrapper.find('textarea'); const dummyContent = 'some new content'; textarea.setValue(dummyContent); expect(updateDraftSpy).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent); }); }); });