diff options
Diffstat (limited to 'spec/javascripts/notes/components')
12 files changed, 0 insertions, 1422 deletions
diff --git a/spec/javascripts/notes/components/diff_with_note_spec.js b/spec/javascripts/notes/components/diff_with_note_spec.js deleted file mode 100644 index 573aac2c3e0..00000000000 --- a/spec/javascripts/notes/components/diff_with_note_spec.js +++ /dev/null @@ -1,89 +0,0 @@ -import Vue from 'vue'; -import { mountComponentWithStore } from 'spec/helpers'; -import DiffWithNote from '~/notes/components/diff_with_note.vue'; -import { createStore } from '~/mr_notes/stores'; - -const discussionFixture = 'merge_requests/diff_discussion.json'; -const imageDiscussionFixture = 'merge_requests/image_diff_discussion.json'; - -describe('diff_with_note', () => { - let store; - let vm; - const diffDiscussionMock = getJSONFixture(discussionFixture)[0]; - const diffDiscussion = diffDiscussionMock; - const Component = Vue.extend(DiffWithNote); - const props = { - discussion: diffDiscussion, - }; - const selectors = { - get container() { - return vm.$el; - }, - get diffTable() { - return this.container.querySelector('.diff-content table'); - }, - get diffRows() { - return this.container.querySelectorAll('.diff-content .line_holder'); - }, - get noteRow() { - return this.container.querySelector('.diff-content .notes_holder'); - }, - }; - - beforeEach(() => { - store = createStore(); - store.replaceState({ - ...store.state, - notes: { - noteableData: { - current_user: {}, - }, - }, - }); - }); - - describe('text diff', () => { - beforeEach(() => { - vm = mountComponentWithStore(Component, { props, store }); - }); - - it('removes trailing "+" char', () => { - const richText = vm.$el.querySelectorAll('.line_holder')[4].querySelector('.line_content') - .textContent[0]; - - expect(richText).not.toEqual('+'); - }); - - it('removes trailing "-" char', () => { - const richText = vm.$el.querySelector('#LC13').parentNode.textContent[0]; - - expect(richText).not.toEqual('-'); - }); - - it('shows text diff', () => { - expect(selectors.container).toHaveClass('text-file'); - expect(selectors.diffTable).toExist(); - }); - - it('shows diff lines', () => { - expect(selectors.diffRows.length).toBe(12); - }); - - it('shows notes row', () => { - expect(selectors.noteRow).toExist(); - }); - }); - - describe('image diff', () => { - beforeEach(() => { - const imageDiffDiscussionMock = getJSONFixture(imageDiscussionFixture)[0]; - props.discussion = imageDiffDiscussionMock; - }); - - it('shows image diff', () => { - vm = mountComponentWithStore(Component, { props, store }); - - expect(selectors.diffTable).not.toExist(); - }); - }); -}); diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js deleted file mode 100644 index 7524de36ac5..00000000000 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ /dev/null @@ -1,187 +0,0 @@ -import Vue from 'vue'; -import createStore from '~/notes/stores'; -import DiscussionFilter from '~/notes/components/discussion_filter.vue'; -import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants'; -import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { discussionFiltersMock, discussionMock } from '../mock_data'; - -describe('DiscussionFilter component', () => { - let vm; - let store; - let eventHub; - - const mountComponent = () => { - store = createStore(); - - const discussions = [ - { - ...discussionMock, - id: discussionMock.id, - notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }], - }, - ]; - const Component = Vue.extend(DiscussionFilter); - const selectedValue = DISCUSSION_FILTERS_DEFAULT_VALUE; - const props = { filters: discussionFiltersMock, selectedValue }; - - store.state.discussions = discussions; - return mountComponentWithStore(Component, { - el: null, - store, - props, - }); - }; - - beforeEach(() => { - window.mrTabs = undefined; - vm = mountComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders the all filters', () => { - expect(vm.$el.querySelectorAll('.dropdown-menu li').length).toEqual( - discussionFiltersMock.length, - ); - }); - - it('renders the default selected item', () => { - expect(vm.$el.querySelector('#discussion-filter-dropdown').textContent.trim()).toEqual( - discussionFiltersMock[0].title, - ); - }); - - it('updates to the selected item', () => { - const filterItem = vm.$el.querySelector( - `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`, - ); - filterItem.click(); - - expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim()); - }); - - it('only updates when selected filter changes', () => { - const filterItem = vm.$el.querySelector( - `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`, - ); - - spyOn(vm, 'filterDiscussion'); - filterItem.click(); - - expect(vm.filterDiscussion).not.toHaveBeenCalled(); - }); - - it('disables commenting when "Show history only" filter is applied', () => { - const filterItem = vm.$el.querySelector( - `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`, - ); - filterItem.click(); - - expect(vm.$store.state.commentsDisabled).toBe(true); - }); - - it('enables commenting when "Show history only" filter is not applied', () => { - const filterItem = vm.$el.querySelector( - `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`, - ); - filterItem.click(); - - expect(vm.$store.state.commentsDisabled).toBe(false); - }); - - it('renders a dropdown divider for the default filter', () => { - const defaultFilter = vm.$el.querySelector( - `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`, - ); - - expect(defaultFilter.lastChild.classList).toContain('dropdown-divider'); - }); - - describe('Merge request tabs', () => { - eventHub = new Vue(); - - beforeEach(() => { - window.mrTabs = { - eventHub, - currentTab: 'show', - }; - - vm = mountComponent(); - }); - - afterEach(() => { - window.mrTabs = undefined; - }); - - it('only renders when discussion tab is active', done => { - eventHub.$emit('MergeRequestTabChange', 'commit'); - - vm.$nextTick(() => { - expect(vm.$el.querySelector).toBeUndefined(); - done(); - }); - }); - }); - - describe('URL with Links to notes', () => { - afterEach(() => { - window.location.hash = ''; - }); - - it('updates the filter when the URL links to a note', done => { - window.location.hash = `note_${discussionMock.notes[0].id}`; - vm.currentValue = discussionFiltersMock[2].value; - vm.handleLocationHash(); - - vm.$nextTick(() => { - expect(vm.currentValue).toEqual(DISCUSSION_FILTERS_DEFAULT_VALUE); - done(); - }); - }); - - it('does not update the filter when the current filter is "Show all activity"', done => { - window.location.hash = `note_${discussionMock.notes[0].id}`; - vm.handleLocationHash(); - - vm.$nextTick(() => { - expect(vm.currentValue).toEqual(DISCUSSION_FILTERS_DEFAULT_VALUE); - done(); - }); - }); - - it('only updates filter when the URL links to a note', done => { - window.location.hash = `testing123`; - vm.handleLocationHash(); - - vm.$nextTick(() => { - expect(vm.currentValue).toEqual(DISCUSSION_FILTERS_DEFAULT_VALUE); - done(); - }); - }); - - it('fetches discussions when there is a hash', done => { - window.location.hash = `note_${discussionMock.notes[0].id}`; - vm.currentValue = discussionFiltersMock[2].value; - spyOn(vm, 'selectFilter'); - vm.handleLocationHash(); - - vm.$nextTick(() => { - expect(vm.selectFilter).toHaveBeenCalled(); - done(); - }); - }); - - it('does not fetch discussions when there is no hash', done => { - window.location.hash = ''; - spyOn(vm, 'selectFilter'); - vm.handleLocationHash(); - - vm.$nextTick(() => { - expect(vm.selectFilter).not.toHaveBeenCalled(); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js deleted file mode 100644 index 4348445f7ca..00000000000 --- a/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js +++ /dev/null @@ -1,30 +0,0 @@ -import { GlButton } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { TEST_HOST } from 'spec/test_constants'; -import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue'; - -const localVue = createLocalVue(); - -describe('ResolveWithIssueButton', () => { - let wrapper; - const url = `${TEST_HOST}/hello-world/`; - - beforeEach(() => { - wrapper = shallowMount(ResolveWithIssueButton, { - localVue, - propsData: { - url, - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('it should have a link with the provided link property as href', () => { - const button = wrapper.find(GlButton); - - expect(button.attributes().href).toBe(url); - }); -}); diff --git a/spec/javascripts/notes/components/note_actions/reply_button_spec.js b/spec/javascripts/notes/components/note_actions/reply_button_spec.js deleted file mode 100644 index 720ab10b270..00000000000 --- a/spec/javascripts/notes/components/note_actions/reply_button_spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vuex from 'vuex'; -import { createLocalVue, mount } from '@vue/test-utils'; -import ReplyButton from '~/notes/components/note_actions/reply_button.vue'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('ReplyButton', () => { - let wrapper; - - beforeEach(() => { - wrapper = mount(localVue.extend(ReplyButton), { - localVue, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('emits startReplying on click', () => { - const button = wrapper.find({ ref: 'button' }); - - button.trigger('click'); - - expect(wrapper.emitted().startReplying).toBeTruthy(); - expect(wrapper.emitted().startReplying.length).toBe(1); - }); -}); diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js deleted file mode 100644 index 5d13f587ca7..00000000000 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ /dev/null @@ -1,160 +0,0 @@ -import Vue from 'vue'; -import { shallowMount, createLocalVue, createWrapper } from '@vue/test-utils'; -import { TEST_HOST } from 'spec/test_constants'; -import createStore from '~/notes/stores'; -import noteActions from '~/notes/components/note_actions.vue'; -import { userDataMock } from '../mock_data'; - -describe('noteActions', () => { - let wrapper; - let store; - let props; - - const shallowMountNoteActions = propsData => { - const localVue = createLocalVue(); - return shallowMount(localVue.extend(noteActions), { - store, - propsData, - localVue, - }); - }; - - beforeEach(() => { - store = createStore(); - props = { - accessLevel: 'Maintainer', - authorId: 26, - canDelete: true, - canEdit: true, - canAwardEmoji: true, - canReportAsAbuse: true, - noteId: '539', - noteUrl: `${TEST_HOST}/group/project/-/merge_requests/1#note_1`, - reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26`, - showReply: false, - }; - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('user is logged in', () => { - beforeEach(() => { - store.dispatch('setUserData', userDataMock); - - wrapper = shallowMountNoteActions(props); - }); - - it('should render access level badge', () => { - expect( - wrapper - .find('.note-role') - .text() - .trim(), - ).toEqual(props.accessLevel); - }); - - it('should render emoji link', () => { - expect(wrapper.find('.js-add-award').exists()).toBe(true); - expect(wrapper.find('.js-add-award').attributes('data-position')).toBe('right'); - }); - - describe('actions dropdown', () => { - it('should be possible to edit the comment', () => { - expect(wrapper.find('.js-note-edit').exists()).toBe(true); - }); - - it('should be possible to report abuse to admin', () => { - expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true); - }); - - it('should be possible to copy link to a note', () => { - expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(true); - }); - - it('should not show copy link action when `noteUrl` prop is empty', done => { - wrapper.setProps({ - ...props, - noteUrl: '', - }); - - Vue.nextTick() - .then(() => { - expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false); - }) - .then(done) - .catch(done.fail); - }); - - it('should be possible to delete comment', () => { - expect(wrapper.find('.js-note-delete').exists()).toBe(true); - }); - - it('closes tooltip when dropdown opens', done => { - wrapper.find('.more-actions-toggle').trigger('click'); - - const rootWrapper = createWrapper(wrapper.vm.$root); - Vue.nextTick() - .then(() => { - const emitted = Object.keys(rootWrapper.emitted()); - - expect(emitted).toEqual(['bv::hide::tooltip']); - done(); - }) - .catch(done.fail); - }); - }); - }); - - describe('user is not logged in', () => { - beforeEach(() => { - store.dispatch('setUserData', {}); - wrapper = shallowMountNoteActions({ - ...props, - canDelete: false, - canEdit: false, - canAwardEmoji: false, - canReportAsAbuse: false, - }); - }); - - it('should not render emoji link', () => { - expect(wrapper.find('.js-add-award').exists()).toBe(false); - }); - - it('should not render actions dropdown', () => { - expect(wrapper.find('.more-actions').exists()).toBe(false); - }); - }); - - describe('for showReply = true', () => { - beforeEach(() => { - wrapper = shallowMountNoteActions({ - ...props, - showReply: true, - }); - }); - - it('shows a reply button', () => { - const replyButton = wrapper.find({ ref: 'replyButton' }); - - expect(replyButton.exists()).toBe(true); - }); - }); - - describe('for showReply = false', () => { - beforeEach(() => { - wrapper = shallowMountNoteActions({ - ...props, - showReply: false, - }); - }); - - it('does not show a reply button', () => { - const replyButton = wrapper.find({ ref: 'replyButton' }); - - expect(replyButton.exists()).toBe(false); - }); - }); -}); diff --git a/spec/javascripts/notes/components/note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js deleted file mode 100644 index 90aa1684272..00000000000 --- a/spec/javascripts/notes/components/note_awards_list_spec.js +++ /dev/null @@ -1,152 +0,0 @@ -import Vue from 'vue'; -import createStore from '~/notes/stores'; -import awardsNote from '~/notes/components/note_awards_list.vue'; -import { noteableDataMock, notesDataMock } from '../mock_data'; - -describe('note_awards_list component', () => { - let store; - let vm; - let awardsMock; - - beforeEach(() => { - const Component = Vue.extend(awardsNote); - - store = createStore(); - store.dispatch('setNoteableData', noteableDataMock); - store.dispatch('setNotesData', notesDataMock); - awardsMock = [ - { - name: 'flag_tz', - user: { id: 1, name: 'Administrator', username: 'root' }, - }, - { - name: 'cartwheel_tone3', - user: { id: 12, name: 'Bobbie Stehr', username: 'erin' }, - }, - ]; - - vm = new Component({ - store, - propsData: { - awards: awardsMock, - noteAuthorId: 2, - noteId: '545', - canAwardEmoji: true, - toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji', - }, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render awarded emojis', () => { - expect(vm.$el.querySelector('.js-awards-block button [data-name="flag_tz"]')).toBeDefined(); - expect( - vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]'), - ).toBeDefined(); - }); - - it('should be possible to remove awarded emoji', () => { - spyOn(vm, 'handleAward').and.callThrough(); - spyOn(vm, 'toggleAwardRequest').and.callThrough(); - vm.$el.querySelector('.js-awards-block button').click(); - - expect(vm.handleAward).toHaveBeenCalledWith('flag_tz'); - expect(vm.toggleAwardRequest).toHaveBeenCalled(); - }); - - it('should be possible to add new emoji', () => { - expect(vm.$el.querySelector('.js-add-award')).toBeDefined(); - }); - - describe('when the user name contains special HTML characters', () => { - const createAwardEmoji = (_, index) => ({ - name: 'art', - user: { id: index, name: `&<>"\`'-${index}`, username: `user-${index}` }, - }); - - const mountComponent = () => { - const Component = Vue.extend(awardsNote); - vm = new Component({ - store, - propsData: { - awards: awardsMock, - noteAuthorId: 0, - noteId: '545', - canAwardEmoji: true, - toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji', - }, - }).$mount(); - }; - - const findTooltip = () => - vm.$el.querySelector('[data-original-title]').getAttribute('data-original-title'); - - it('should only escape & and " characters', () => { - awardsMock = [...new Array(1)].map(createAwardEmoji); - mountComponent(); - const escapedName = awardsMock[0].user.name.replace(/&/g, '&').replace(/"/g, '"'); - - expect(vm.$el.querySelector('[data-original-title]').outerHTML).toContain(escapedName); - }); - - it('should not escape special HTML characters twice when only 1 person awarded', () => { - awardsMock = [...new Array(1)].map(createAwardEmoji); - mountComponent(); - - awardsMock.forEach(award => { - expect(findTooltip()).toContain(award.user.name); - }); - }); - - it('should not escape special HTML characters twice when 2 people awarded', () => { - awardsMock = [...new Array(2)].map(createAwardEmoji); - mountComponent(); - - awardsMock.forEach(award => { - expect(findTooltip()).toContain(award.user.name); - }); - }); - - it('should not escape special HTML characters twice when more than 10 people awarded', () => { - awardsMock = [...new Array(11)].map(createAwardEmoji); - mountComponent(); - - // Testing only the first 10 awards since 11 onward will not be displayed. - awardsMock.slice(0, 10).forEach(award => { - expect(findTooltip()).toContain(award.user.name); - }); - }); - }); - - describe('when the user cannot award emoji', () => { - beforeEach(() => { - const Component = Vue.extend(awardsNote); - - vm = new Component({ - store, - propsData: { - awards: awardsMock, - noteAuthorId: 2, - noteId: '545', - canAwardEmoji: false, - toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji', - }, - }).$mount(); - }); - - it('should not be possible to remove awarded emoji', () => { - spyOn(vm, 'toggleAwardRequest').and.callThrough(); - - vm.$el.querySelector('.js-awards-block button').click(); - - expect(vm.toggleAwardRequest).not.toHaveBeenCalled(); - }); - - it('should not be possible to add new emoji', () => { - expect(vm.$el.querySelector('.js-add-award')).toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/notes/components/note_body_spec.js b/spec/javascripts/notes/components/note_body_spec.js deleted file mode 100644 index efad0785afe..00000000000 --- a/spec/javascripts/notes/components/note_body_spec.js +++ /dev/null @@ -1,57 +0,0 @@ -import Vue from 'vue'; -import createStore from '~/notes/stores'; -import noteBody from '~/notes/components/note_body.vue'; -import { noteableDataMock, notesDataMock, note } from '../mock_data'; - -describe('issue_note_body component', () => { - let store; - let vm; - - beforeEach(() => { - const Component = Vue.extend(noteBody); - - store = createStore(); - store.dispatch('setNoteableData', noteableDataMock); - store.dispatch('setNotesData', notesDataMock); - - vm = new Component({ - store, - propsData: { - note, - canEdit: true, - canAwardEmoji: true, - }, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render the note', () => { - expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html); - }); - - it('should render awards list', () => { - expect(vm.$el.querySelector('.js-awards-block button [data-name="baseball"]')).not.toBeNull(); - expect(vm.$el.querySelector('.js-awards-block button [data-name="bath_tone3"]')).not.toBeNull(); - }); - - describe('isEditing', () => { - beforeEach(done => { - vm.isEditing = true; - Vue.nextTick(done); - }); - - it('renders edit form', () => { - expect(vm.$el.querySelector('textarea.js-task-list-field')).not.toBeNull(); - }); - - it('adds autosave', () => { - const autosaveKey = `autosave/Note/${note.noteable_type}/${note.id}`; - - expect(vm.autosave).toExist(); - expect(vm.autosave.key).toEqual(autosaveKey); - }); - }); -}); diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js deleted file mode 100644 index 8ab8bce9027..00000000000 --- a/spec/javascripts/notes/components/note_form_spec.js +++ /dev/null @@ -1,267 +0,0 @@ -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); - }); - }); -}); diff --git a/spec/javascripts/notes/components/note_signed_out_widget_spec.js b/spec/javascripts/notes/components/note_signed_out_widget_spec.js deleted file mode 100644 index e217a2caa73..00000000000 --- a/spec/javascripts/notes/components/note_signed_out_widget_spec.js +++ /dev/null @@ -1,41 +0,0 @@ -import Vue from 'vue'; -import noteSignedOut from '~/notes/components/note_signed_out_widget.vue'; -import createStore from '~/notes/stores'; -import { notesDataMock } from '../mock_data'; - -describe('note_signed_out_widget component', () => { - let store; - let vm; - - beforeEach(() => { - const Component = Vue.extend(noteSignedOut); - store = createStore(); - store.dispatch('setNotesData', notesDataMock); - - vm = new Component({ - store, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render sign in link provided in the store', () => { - expect(vm.$el.querySelector(`a[href="${notesDataMock.newSessionPath}"]`).textContent).toEqual( - 'sign in', - ); - }); - - it('should render register link provided in the store', () => { - expect(vm.$el.querySelector(`a[href="${notesDataMock.registerPath}"]`).textContent).toEqual( - 'register', - ); - }); - - it('should render information text', () => { - expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual( - 'Please register or sign in to reply', - ); - }); -}); diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js deleted file mode 100644 index ee84fd2b091..00000000000 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ /dev/null @@ -1,195 +0,0 @@ -import { mount, createLocalVue } from '@vue/test-utils'; -import createStore from '~/notes/stores'; -import noteableDiscussion from '~/notes/components/noteable_discussion.vue'; -import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; -import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue'; -import NoteForm from '~/notes/components/note_form.vue'; -import '~/behaviors/markdown/render_gfm'; -import { - noteableDataMock, - discussionMock, - notesDataMock, - loggedOutnoteableData, - userDataMock, -} from '../mock_data'; -import mockDiffFile from '../../diffs/mock_data/diff_file'; -import { trimText } from '../../helpers/text_helper'; - -const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json'; - -const localVue = createLocalVue(); - -describe('noteable_discussion component', () => { - let store; - let wrapper; - let originalGon; - - preloadFixtures(discussionWithTwoUnresolvedNotes); - - beforeEach(() => { - window.mrTabs = {}; - store = createStore(); - store.dispatch('setNoteableData', noteableDataMock); - store.dispatch('setNotesData', notesDataMock); - - wrapper = mount(localVue.extend(noteableDiscussion), { - store, - propsData: { discussion: discussionMock }, - localVue, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should not render thread header for non diff threads', () => { - expect(wrapper.find('.discussion-header').exists()).toBe(false); - }); - - it('should render thread header', done => { - const discussion = { ...discussionMock }; - discussion.diff_file = mockDiffFile; - discussion.diff_discussion = true; - - wrapper.setProps({ discussion }); - - wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.find('.discussion-header').exists()).toBe(true); - }) - .then(done) - .catch(done.fail); - }); - - describe('actions', () => { - it('should toggle reply form', done => { - const replyPlaceholder = wrapper.find(ReplyPlaceholder); - - wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.vm.isReplying).toEqual(false); - - replyPlaceholder.vm.$emit('onClick'); - }) - .then(() => wrapper.vm.$nextTick()) - .then(() => { - expect(wrapper.vm.isReplying).toEqual(true); - - const noteForm = wrapper.find(NoteForm); - - expect(noteForm.exists()).toBe(true); - - const noteFormProps = noteForm.props(); - - expect(noteFormProps.discussion).toBe(discussionMock); - expect(noteFormProps.isEditing).toBe(false); - expect(noteFormProps.line).toBe(null); - expect(noteFormProps.saveButtonTitle).toBe('Comment'); - expect(noteFormProps.autosaveKey).toBe(`Note/Issue/${discussionMock.id}/Reply`); - }) - .then(done) - .catch(done.fail); - }); - - it('does not render jump to thread button', () => { - expect(wrapper.find('*[data-original-title="Jump to next unresolved thread"]').exists()).toBe( - false, - ); - }); - }); - - describe('for resolved thread', () => { - beforeEach(() => { - const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0]; - wrapper.setProps({ discussion }); - }); - - it('does not display a button to resolve with issue', () => { - const button = wrapper.find(ResolveWithIssueButton); - - expect(button.exists()).toBe(false); - }); - }); - - describe('for unresolved thread', () => { - beforeEach(done => { - const discussion = { - ...getJSONFixture(discussionWithTwoUnresolvedNotes)[0], - expanded: true, - }; - discussion.notes = discussion.notes.map(note => ({ - ...note, - resolved: false, - current_user: { - ...note.current_user, - can_resolve: true, - }, - })); - - wrapper.setProps({ discussion }); - - wrapper.vm - .$nextTick() - .then(done) - .catch(done.fail); - }); - - it('displays a button to resolve with issue', () => { - const button = wrapper.find(ResolveWithIssueButton); - - expect(button.exists()).toBe(true); - }); - }); - - describe('signout widget', () => { - beforeEach(() => { - originalGon = Object.assign({}, window.gon); - window.gon = window.gon || {}; - }); - - afterEach(() => { - wrapper.destroy(); - window.gon = originalGon; - }); - - describe('user is logged in', () => { - beforeEach(() => { - window.gon.current_user_id = userDataMock.id; - store.dispatch('setUserData', userDataMock); - - wrapper = mount(localVue.extend(noteableDiscussion), { - store, - propsData: { discussion: discussionMock }, - localVue, - }); - }); - - it('should not render signed out widget', () => { - expect(Boolean(wrapper.vm.isLoggedIn)).toBe(true); - expect(trimText(wrapper.text())).not.toContain('Please register or sign in to reply'); - }); - }); - - describe('user is not logged in', () => { - beforeEach(() => { - window.gon.current_user_id = null; - store.dispatch('setNoteableData', loggedOutnoteableData); - store.dispatch('setNotesData', notesDataMock); - - wrapper = mount(localVue.extend(noteableDiscussion), { - store, - propsData: { discussion: discussionMock }, - localVue, - }); - }); - - it('should render signed out widget', () => { - expect(Boolean(wrapper.vm.isLoggedIn)).toBe(false); - expect(trimText(wrapper.text())).toContain('Please register or sign in to reply'); - }); - }); - }); -}); diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js deleted file mode 100644 index 1906dae7800..00000000000 --- a/spec/javascripts/notes/components/noteable_note_spec.js +++ /dev/null @@ -1,137 +0,0 @@ -import { escape } from 'lodash'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import createStore from '~/notes/stores'; -import issueNote from '~/notes/components/noteable_note.vue'; -import NoteHeader from '~/notes/components/note_header.vue'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; -import NoteActions from '~/notes/components/note_actions.vue'; -import NoteBody from '~/notes/components/note_body.vue'; -import { noteableDataMock, notesDataMock, note } from '../mock_data'; - -describe('issue_note', () => { - let store; - let wrapper; - - beforeEach(() => { - store = createStore(); - store.dispatch('setNoteableData', noteableDataMock); - store.dispatch('setNotesData', notesDataMock); - - const localVue = createLocalVue(); - wrapper = shallowMount(localVue.extend(issueNote), { - store, - propsData: { - note, - }, - localVue, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render user information', () => { - const { author } = note; - const avatar = wrapper.find(UserAvatarLink); - const avatarProps = avatar.props(); - - expect(avatarProps.linkHref).toBe(author.path); - expect(avatarProps.imgSrc).toBe(author.avatar_url); - expect(avatarProps.imgAlt).toBe(author.name); - expect(avatarProps.imgSize).toBe(40); - }); - - it('should render note header content', () => { - const noteHeader = wrapper.find(NoteHeader); - const noteHeaderProps = noteHeader.props(); - - expect(noteHeaderProps.author).toEqual(note.author); - expect(noteHeaderProps.createdAt).toEqual(note.created_at); - expect(noteHeaderProps.noteId).toEqual(note.id); - }); - - it('should render note actions', () => { - const { author } = note; - const noteActions = wrapper.find(NoteActions); - const noteActionsProps = noteActions.props(); - - expect(noteActionsProps.authorId).toBe(author.id); - expect(noteActionsProps.noteId).toBe(note.id); - expect(noteActionsProps.noteUrl).toBe(note.noteable_note_url); - expect(noteActionsProps.accessLevel).toBe(note.human_access); - expect(noteActionsProps.canEdit).toBe(note.current_user.can_edit); - expect(noteActionsProps.canAwardEmoji).toBe(note.current_user.can_award_emoji); - expect(noteActionsProps.canDelete).toBe(note.current_user.can_edit); - expect(noteActionsProps.canReportAsAbuse).toBe(true); - expect(noteActionsProps.canResolve).toBe(false); - expect(noteActionsProps.reportAbusePath).toBe(note.report_abuse_path); - expect(noteActionsProps.resolvable).toBe(false); - expect(noteActionsProps.isResolved).toBe(false); - expect(noteActionsProps.isResolving).toBe(false); - expect(noteActionsProps.resolvedBy).toEqual({}); - }); - - it('should render issue body', () => { - const noteBody = wrapper.find(NoteBody); - const noteBodyProps = noteBody.props(); - - expect(noteBodyProps.note).toEqual(note); - expect(noteBodyProps.line).toBe(null); - expect(noteBodyProps.canEdit).toBe(note.current_user.can_edit); - expect(noteBodyProps.isEditing).toBe(false); - expect(noteBodyProps.helpPagePath).toBe(''); - }); - - it('prevents note preview xss', done => { - const imgSrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; - const noteBody = `<img src="${imgSrc}" onload="alert(1)" />`; - const alertSpy = spyOn(window, 'alert'); - store.hotUpdate({ - actions: { - updateNote() {}, - }, - }); - const noteBodyComponent = wrapper.find(NoteBody); - - noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {}); - - setTimeout(() => { - expect(alertSpy).not.toHaveBeenCalled(); - expect(wrapper.vm.note.note_html).toEqual(escape(noteBody)); - done(); - }, 0); - }); - - describe('cancel edit', () => { - it('restores content of updated note', done => { - const updatedText = 'updated note text'; - store.hotUpdate({ - actions: { - updateNote() {}, - }, - }); - const noteBody = wrapper.find(NoteBody); - noteBody.vm.resetAutoSave = () => {}; - - noteBody.vm.$emit('handleFormUpdate', updatedText, null, () => {}); - - wrapper.vm - .$nextTick() - .then(() => { - const noteBodyProps = noteBody.props(); - - expect(noteBodyProps.note.note_html).toBe(updatedText); - noteBody.vm.$emit('cancelForm'); - }) - .then(() => wrapper.vm.$nextTick()) - .then(() => { - const noteBodyProps = noteBody.props(); - - expect(noteBodyProps.note.note_html).toBe(note.note_html); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/notes/components/toggle_replies_widget_spec.js b/spec/javascripts/notes/components/toggle_replies_widget_spec.js deleted file mode 100644 index 8485ec0262f..00000000000 --- a/spec/javascripts/notes/components/toggle_replies_widget_spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import toggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue'; -import { note } from '../mock_data'; - -const deepCloneObject = obj => JSON.parse(JSON.stringify(obj)); - -describe('toggle replies widget for notes', () => { - let vm; - let ToggleRepliesWidget; - const noteFromOtherUser = deepCloneObject(note); - noteFromOtherUser.author.username = 'fatihacet'; - - const noteFromAnotherUser = deepCloneObject(note); - noteFromAnotherUser.author.username = 'mgreiling'; - noteFromAnotherUser.author.name = 'Mike Greiling'; - - const replies = [note, note, note, noteFromOtherUser, noteFromAnotherUser]; - - beforeEach(() => { - ToggleRepliesWidget = Vue.extend(toggleRepliesWidget); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('collapsed state', () => { - beforeEach(() => { - vm = mountComponent(ToggleRepliesWidget, { - replies, - collapsed: true, - }); - }); - - it('should render the collapsed', () => { - const vmTextContent = vm.$el.textContent.replace(/\s\s+/g, ' '); - - expect(vm.$el.classList.contains('collapsed')).toEqual(true); - expect(vm.$el.querySelectorAll('.user-avatar-link').length).toEqual(3); - expect(vm.$el.querySelector('time')).not.toBeNull(); - expect(vmTextContent).toContain('5 replies'); - expect(vmTextContent).toContain(`Last reply by ${noteFromAnotherUser.author.name}`); - }); - - it('should emit toggle event when the replies text clicked', () => { - const spy = spyOn(vm, '$emit'); - - vm.$el.querySelector('.js-replies-text').click(); - - expect(spy).toHaveBeenCalledWith('toggle'); - }); - }); - - describe('expanded state', () => { - beforeEach(() => { - vm = mountComponent(ToggleRepliesWidget, { - replies, - collapsed: false, - }); - }); - - it('should render expanded state', () => { - const vmTextContent = vm.$el.textContent.replace(/\s\s+/g, ' '); - - expect(vm.$el.querySelector('.collapse-replies-btn')).not.toBeNull(); - expect(vmTextContent).toContain('Collapse replies'); - }); - - it('should emit toggle event when the collapse replies text called', () => { - const spy = spyOn(vm, '$emit'); - - vm.$el.querySelector('.js-collapse-replies').click(); - - expect(spy).toHaveBeenCalledWith('toggle'); - }); - }); -}); |