diff options
author | Fatih Acet <acetfatih@gmail.com> | 2018-02-28 00:10:43 +0000 |
---|---|---|
committer | Jacob Schatz <jschatz@gitlab.com> | 2018-02-28 00:10:43 +0000 |
commit | 059ab73b8eae3a546d0a19fe99ef0c52df5fac01 (patch) | |
tree | b24d4162072c0099147fcdb4f19f95511689ff6e /spec/javascripts | |
parent | 0be4a77d0012613f960c4177f53101c46de2899c (diff) | |
download | gitlab-ce-059ab73b8eae3a546d0a19fe99ef0c52df5fac01.tar.gz |
Render MR Notes with Vue with behind a cookie
Diffstat (limited to 'spec/javascripts')
-rw-r--r-- | spec/javascripts/autosave_spec.js | 55 | ||||
-rw-r--r-- | spec/javascripts/fixtures/merge_requests.rb | 42 | ||||
-rw-r--r-- | spec/javascripts/notes/components/comment_form_spec.js | 23 | ||||
-rw-r--r-- | spec/javascripts/notes/components/diff_file_header_spec.js | 93 | ||||
-rw-r--r-- | spec/javascripts/notes/components/diff_with_note_spec.js | 64 | ||||
-rw-r--r-- | spec/javascripts/notes/components/note_app_spec.js | 5 | ||||
-rw-r--r-- | spec/javascripts/notes/components/note_body_spec.js | 27 | ||||
-rw-r--r-- | spec/javascripts/notes/components/note_header_spec.js | 32 | ||||
-rw-r--r-- | spec/javascripts/notes/mock_data.js | 5 | ||||
-rw-r--r-- | spec/javascripts/notes/stores/getters_spec.js | 4 | ||||
-rw-r--r-- | spec/javascripts/notes/stores/mutation_spec.js | 5 | ||||
-rw-r--r-- | spec/javascripts/notes_spec.js | 11 |
12 files changed, 319 insertions, 47 deletions
diff --git a/spec/javascripts/autosave_spec.js b/spec/javascripts/autosave_spec.js index 9f9acc392c2..b568d7fa8b0 100644 --- a/spec/javascripts/autosave_spec.js +++ b/spec/javascripts/autosave_spec.js @@ -3,28 +3,24 @@ import AccessorUtilities from '~/lib/utils/accessor'; describe('Autosave', () => { let autosave; + const field = $('<textarea></textarea>'); + const key = 'key'; describe('class constructor', () => { - const key = 'key'; - const field = jasmine.createSpyObj('field', ['data', 'on']); - beforeEach(() => { spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true); spyOn(Autosave.prototype, 'restore'); - - autosave = new Autosave(field, key); }); it('should set .isLocalStorageAvailable', () => { + autosave = new Autosave(field, key); + expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); expect(autosave.isLocalStorageAvailable).toBe(true); }); }); describe('restore', () => { - const key = 'key'; - const field = jasmine.createSpyObj('field', ['trigger']); - beforeEach(() => { autosave = { field, @@ -49,24 +45,53 @@ describe('Autosave', () => { describe('if .isLocalStorageAvailable is `true`', () => { beforeEach(() => { autosave.isLocalStorageAvailable = true; - - Autosave.prototype.restore.call(autosave); }); it('should call .getItem', () => { + Autosave.prototype.restore.call(autosave); + expect(window.localStorage.getItem).toHaveBeenCalledWith(key); }); + + it('triggers jquery event', () => { + spyOn(autosave.field, 'trigger').and.callThrough(); + + Autosave.prototype.restore.call(autosave); + + expect( + field.trigger, + ).toHaveBeenCalled(); + }); + + it('triggers native event', (done) => { + autosave.field.get(0).addEventListener('change', () => { + done(); + }); + + Autosave.prototype.restore.call(autosave); + }); + }); + + describe('if field gets deleted from DOM', () => { + beforeEach(() => { + autosave.field = $('.not-a-real-element'); + }); + + it('does not trigger event', () => { + spyOn(field, 'trigger').and.callThrough(); + + expect( + field.trigger, + ).not.toHaveBeenCalled(); + }); }); }); describe('save', () => { - const field = jasmine.createSpyObj('field', ['val']); - beforeEach(() => { autosave = jasmine.createSpyObj('autosave', ['reset']); autosave.field = field; - - field.val.and.returnValue('value'); + field.val('value'); spyOn(window.localStorage, 'setItem'); }); @@ -97,8 +122,6 @@ describe('Autosave', () => { }); describe('reset', () => { - const key = 'key'; - beforeEach(() => { autosave = { key, diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 3fd16d76f51..ee60489eb7c 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -70,8 +70,50 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merge_request) end + it 'merge_requests/discussions.json' do |example| + create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) + render_discussions_json(merge_request, example.description) + end + + it 'merge_requests/diff_discussion.json' do |example| + create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) + render_discussions_json(merge_request, example.description) + end + + context 'with image diff' do + let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images") } + let(:image_path) { "files/images/ee_repo_logo.png" } + let(:image_position) do + Gitlab::Diff::Position.new( + old_path: image_path, + new_path: image_path, + width: 100, + height: 100, + x: 1, + y: 1, + position_type: "image", + diff_refs: merge_request2.diff_refs + ) + end + + it 'merge_requests/image_diff_discussion.json' do |example| + create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position) + render_discussions_json(merge_request2, example.description) + end + end + private + def render_discussions_json(merge_request, fixture_file_name) + get :discussions, + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.to_param, + format: :json + + store_frontend_fixture(response, fixture_file_name) + end + def render_merge_request(fixture_file_name, merge_request) get :show, namespace_id: project.namespace.to_param, diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js index 104d03377b6..6a7131528a3 100644 --- a/spec/javascripts/notes/components/comment_form_spec.js +++ b/spec/javascripts/notes/components/comment_form_spec.js @@ -1,17 +1,20 @@ import Vue from 'vue'; import Autosize from 'autosize'; import store from '~/notes/stores'; -import issueCommentForm from '~/notes/components/comment_form.vue'; +import CommentForm from '~/notes/components/comment_form.vue'; import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data'; import { keyboardDownEvent } from '../../issue_show/helpers'; describe('issue_comment_form component', () => { let vm; - const Component = Vue.extend(issueCommentForm); + const Component = Vue.extend(CommentForm); let mountComponent; beforeEach(() => { - mountComponent = () => new Component({ + mountComponent = (noteableType = 'issue') => new Component({ + propsData: { + noteableType, + }, store, }).$mount(); }); @@ -136,6 +139,11 @@ describe('issue_comment_form component', () => { expect(vm.editCurrentUserLastNote).toHaveBeenCalled(); }); + + it('inits autosave', () => { + expect(vm.autosave).toBeDefined(); + expect(vm.autosave.key).toEqual(`autosave/Note/Issue/${noteableDataMock.id}`); + }); }); describe('event enter', () => { @@ -182,6 +190,15 @@ describe('issue_comment_form component', () => { done(); }); }); + + it('updates button text with noteable type', (done) => { + vm.noteableType = 'merge_request'; + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.btn-comment-and-close').textContent.trim()).toEqual('Close merge request'); + done(); + }); + }); }); describe('issue is confidential', () => { diff --git a/spec/javascripts/notes/components/diff_file_header_spec.js b/spec/javascripts/notes/components/diff_file_header_spec.js new file mode 100644 index 00000000000..aed30a087a6 --- /dev/null +++ b/spec/javascripts/notes/components/diff_file_header_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import DiffFileHeader from '~/notes/components/diff_file_header.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const discussionFixture = 'merge_requests/diff_discussion.json'; + +describe('diff_file_header', () => { + let vm; + const diffDiscussionMock = getJSONFixture(discussionFixture)[0]; + const diffFile = convertObjectPropsToCamelCase(diffDiscussionMock.diff_file); + const props = { + diffFile, + }; + const Component = Vue.extend(DiffFileHeader); + const selectors = { + get copyButton() { + return vm.$el.querySelector('button[data-original-title="Copy file path to clipboard"]'); + }, + get fileName() { + return vm.$el.querySelector('.file-title-name'); + }, + get titleWrapper() { + return vm.$refs.titleWrapper; + }, + }; + + describe('submodule', () => { + beforeEach(() => { + props.diffFile.submodule = true; + props.diffFile.submoduleLink = '<a href="/bha">Submodule</a>'; + + vm = mountComponent(Component, props); + }); + + it('shows submoduleLink', () => { + expect(selectors.fileName.innerHTML).toBe(props.diffFile.submoduleLink); + }); + + it('has button to copy blob path', () => { + expect(selectors.copyButton).toExist(); + expect(selectors.copyButton.getAttribute('data-clipboard-text')).toBe(props.diffFile.submoduleLink); + }); + }); + + describe('changed file', () => { + beforeEach(() => { + props.diffFile.submodule = false; + props.diffFile.discussionPath = 'some/discussion/id'; + + vm = mountComponent(Component, props); + }); + + it('shows file type icon', () => { + expect(vm.$el.innerHTML).toContain('fa-file-text-o'); + }); + + it('links to discussion path', () => { + expect(selectors.titleWrapper).toExist(); + expect(selectors.titleWrapper.tagName).toBe('A'); + expect(selectors.titleWrapper.getAttribute('href')).toBe(props.diffFile.discussionPath); + }); + + it('shows plain title if no link given', () => { + props.diffFile.discussionPath = undefined; + vm = mountComponent(Component, props); + + expect(selectors.titleWrapper.tagName).not.toBe('A'); + expect(selectors.titleWrapper.href).toBeFalsy(); + }); + + it('has button to copy file path', () => { + expect(selectors.copyButton).toExist(); + expect(selectors.copyButton.getAttribute('data-clipboard-text')).toBe(props.diffFile.filePath); + }); + + it('shows file mode change', (done) => { + vm.diffFile = { + ...props.diffFile, + modeChanged: true, + aMode: '100755', + bMode: '100644', + }; + + Vue.nextTick(() => { + expect( + vm.$refs.fileMode.textContent.trim(), + ).toBe('100755 → 100644'); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/notes/components/diff_with_note_spec.js b/spec/javascripts/notes/components/diff_with_note_spec.js new file mode 100644 index 00000000000..7f1f4bf0bcd --- /dev/null +++ b/spec/javascripts/notes/components/diff_with_note_spec.js @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import DiffWithNote from '~/notes/components/diff_with_note.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const discussionFixture = 'merge_requests/diff_discussion.json'; +const imageDiscussionFixture = 'merge_requests/image_diff_discussion.json'; + +describe('diff_with_note', () => { + let vm; + const diffDiscussionMock = getJSONFixture(discussionFixture)[0]; + const diffDiscussion = convertObjectPropsToCamelCase(diffDiscussionMock); + const Component = Vue.extend(DiffWithNote); + const props = { + discussion: diffDiscussion, + }; + const selectors = { + get container() { + return vm.$refs.fileHolder; + }, + 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'); + }, + }; + + describe('text diff', () => { + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + 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 = convertObjectPropsToCamelCase(imageDiffDiscussionMock); + }); + + it('shows image diff', () => { + vm = mountComponent(Component, props); + + expect(selectors.container).toHaveClass('js-image-file'); + expect(selectors.diffTable).not.toExist(); + }); + }); +}); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index 12d180137a0..e1c612f5100 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -24,6 +24,7 @@ describe('note_app', () => { beforeEach(() => { jasmine.addMatchers(vueMatchers); + $('body').attr('data-page', 'projects:merge_requests:show'); const IssueNotesApp = Vue.extend(notesApp); @@ -119,8 +120,8 @@ describe('note_app', () => { vm = mountComponent(); }); - it('should render loading icon', () => { - expect(vm).toIncludeElement('.js-loading'); + it('renders skeleton notes', () => { + expect(vm).toIncludeElement('.animation-container'); }); it('should render form', () => { diff --git a/spec/javascripts/notes/components/note_body_spec.js b/spec/javascripts/notes/components/note_body_spec.js index b42e7943b98..0ff804f0e55 100644 --- a/spec/javascripts/notes/components/note_body_spec.js +++ b/spec/javascripts/notes/components/note_body_spec.js @@ -30,17 +30,26 @@ describe('issue_note_body component', () => { expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html); }); - it('should be render form if user is editing', (done) => { - vm.isEditing = true; + 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(); + }); - Vue.nextTick(() => { - expect(vm.$el.querySelector('textarea.js-task-list-field')).toBeDefined(); - done(); + describe('isEditing', () => { + beforeEach((done) => { + vm.isEditing = true; + Vue.nextTick(done); }); - }); - it('should render awards list', () => { - expect(vm.$el.querySelector('.js-awards-block button [data-name="baseball"]')).toBeDefined(); - expect(vm.$el.querySelector('.js-awards-block button [data-name="bath_tone3"]')).toBeDefined(); + 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_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js index 16a76b11321..5636f8d1a9f 100644 --- a/spec/javascripts/notes/components/note_header_spec.js +++ b/spec/javascripts/notes/components/note_header_spec.js @@ -32,6 +32,7 @@ describe('note_header component', () => { createdAt: '2017-08-02T10:51:58.559Z', includeToggle: false, noteId: 1394, + expanded: true, }, }).$mount(); }); @@ -68,6 +69,7 @@ describe('note_header component', () => { createdAt: '2017-08-02T10:51:58.559Z', includeToggle: true, noteId: 1395, + expanded: true, }, }).$mount(); }); @@ -76,17 +78,35 @@ describe('note_header component', () => { expect(vm.$el.querySelector('.js-vue-toggle-button')).toBeDefined(); }); - it('should toggle the disucssion icon', (done) => { - expect( - vm.$el.querySelector('.js-vue-toggle-button i').classList.contains('fa-chevron-up'), - ).toEqual(true); + it('emits toggle event on click', (done) => { + spyOn(vm, '$emit'); vm.$el.querySelector('.js-vue-toggle-button').click(); Vue.nextTick(() => { + expect(vm.$emit).toHaveBeenCalledWith('toggleHandler'); + done(); + }); + }); + + it('renders up arrow when open', (done) => { + vm.expanded = true; + + Vue.nextTick(() => { + expect( + vm.$el.querySelector('.js-vue-toggle-button i').classList, + ).toContain('fa-chevron-up'); + done(); + }); + }); + + it('renders down arrow when closed', (done) => { + vm.expanded = false; + + Vue.nextTick(() => { expect( - vm.$el.querySelector('.js-vue-toggle-button i').classList.contains('fa-chevron-down'), - ).toEqual(true); + vm.$el.querySelector('.js-vue-toggle-button i').classList, + ).toContain('fa-chevron-down'); done(); }); }); diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index ccf4bd070c2..bf60cb12f52 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -7,8 +7,9 @@ export const notesDataMock = { notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes', quickActionsDocsPath: '/help/user/project/quick_actions', registerPath: '/users/sign_in?redirect_to_referer=yes#register-pane', - closeIssuePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close', - reopenIssuePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen', + totalNotes: 1, + closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close', + reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen', }; export const userDataMock = { diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js index 919ffbfdef0..8b2a8d2cd7a 100644 --- a/spec/javascripts/notes/stores/getters_spec.js +++ b/spec/javascripts/notes/stores/getters_spec.js @@ -56,9 +56,9 @@ describe('Getters Notes Store', () => { }); }); - describe('issueState', () => { + describe('openState', () => { it('should return the issue state', () => { - expect(getters.issueState(state)).toEqual(noteableDataMock.state); + expect(getters.openState(state)).toEqual(noteableDataMock.state); }); }); }); diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index 22d99998a7d..e4baefc5bfc 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -1,7 +1,7 @@ import mutations from '~/notes/stores/mutations'; import { note, discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data'; -describe('Mutation Notes Store', () => { +describe('Notes Store mutations', () => { describe('ADD_NEW_NOTE', () => { let state; let noteData; @@ -103,7 +103,8 @@ describe('Mutation Notes Store', () => { }; mutations.SET_INITIAL_NOTES(state, [note]); - expect(state.notes).toEqual([note]); + expect(state.notes[0].id).toEqual(note.id); + expect(state.notes.length).toEqual(1); }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 274d7591c71..d4a148e6ab1 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -34,6 +34,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Notes', function() { const FLASH_TYPE_ALERT = 'alert'; + const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; var commentsTemplate = 'merge_requests/merge_request_with_comment.html.raw'; preloadFixtures(commentsTemplate); @@ -154,7 +155,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; $form.find('textarea.js-note-text').val(sampleComment); mock = new MockAdapter(axios); - mock.onPost(/(.*)\/notes$/).reply(200, noteEntity); + mock.onPost(NOTES_POST_PATH).reply(200, noteEntity); }); afterEach(() => { @@ -506,11 +507,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; let mock; function mockNotesPost() { - mock.onPost(/(.*)\/notes$/).reply(200, note); + mock.onPost(NOTES_POST_PATH).reply(200, note); } function mockNotesPostError() { - mock.onPost(/(.*)\/notes$/).networkError(); + mock.onPost(NOTES_POST_PATH).networkError(); } beforeEach(() => { @@ -631,7 +632,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; beforeEach(() => { mock = new MockAdapter(axios); - mock.onPost(/(.*)\/notes$/).reply(200, note); + mock.onPost(NOTES_POST_PATH).reply(200, note); this.notes = new Notes('', []); window.gon.current_username = 'root'; @@ -684,7 +685,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; beforeEach(() => { mock = new MockAdapter(axios); - mock.onPost(/(.*)\/notes$/).reply(200, note); + mock.onPost(NOTES_POST_PATH).reply(200, note); this.notes = new Notes('', []); window.gon.current_username = 'root'; |